From 0b51b63ef24123f27cfda66005b47630d49dd7ac Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Mon, 16 Oct 2023 20:13:04 +0000 Subject: [PATCH] Merged PR 4030: [5.1.2] Fix | Adding type convertor support for SqlConnectionEncryptOption (#2057) Ports [#2057](https://github.com/dotnet/SqlClient/pull/2057) --- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../SqlClient/SqlConnectionEncryptOption.cs | 2 + .../SqlConnectionEncryptOptionConverter.cs | 56 ++++++ .../Microsoft.Data.SqlClient.Tests.csproj | 1 + .../SqlConnectionStringBuilderTest.cs | 160 ++++++++++++++++++ tools/props/Versions.props | 1 + 7 files changed, 226 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index bc755d3486..5e08a713a5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -328,6 +328,9 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index bd8d67b6e2..a10a693406 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -422,6 +422,9 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs index fde49b3980..cb5b467298 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs @@ -3,11 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.ComponentModel; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { /// + [TypeConverter(typeof(SqlConnectionEncryptOptionConverter))] public sealed class SqlConnectionEncryptOption { private const string TRUE = "True"; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs new file mode 100644 index 0000000000..0e22e6ce2e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.ComponentModel; +using System.Globalization; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal class SqlConnectionEncryptOptionConverter : TypeConverter + { + // Overrides the CanConvertFrom method of TypeConverter. + // The ITypeDescriptorContext interface provides the context for the + // conversion. Typically, this interface is used at design time to + // provide information about the design-time container. + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } + + // Overrides the CanConvertTo method of TypeConverter. + public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertTo(context, sourceType); + } + + // Overrides the ConvertFrom method of TypeConverter. + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) + { + return SqlConnectionEncryptOption.Parse(value.ToString()); + } + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionEncryptOption), null); + } + + // Overrides the ConvertTo method of TypeConverter. + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + return base.ConvertTo(context, culture, value, destinationType); + } + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionEncryptOption), null); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 9d1e8d5087..a90960bdc5 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -66,6 +66,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 0825760e45..7e65cec996 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -4,6 +4,13 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Text; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -446,6 +453,159 @@ public void EncryptTryParseInvalidValuesReturnsFalse(string value) Assert.Null(result); } + #region SqlConnectionEncryptOptionCoverterTests + [Fact] + public void ConnectionStringFromJsonTests() + { + UserDbConnectionStringSettings settings = LoadSettingsFromJsonStream("false"); + Assert.Equal(SqlConnectionEncryptOption.Optional, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("true"); + Assert.Equal(SqlConnectionEncryptOption.Mandatory, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("strict"); + Assert.Equal(SqlConnectionEncryptOption.Strict, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("mandatory"); + Assert.Equal(SqlConnectionEncryptOption.Mandatory, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("optional"); + Assert.Equal(SqlConnectionEncryptOption.Optional, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("yes"); + Assert.Equal(SqlConnectionEncryptOption.Mandatory, settings.UserDb.UserComponents.Encrypt); + + settings = LoadSettingsFromJsonStream("no"); + Assert.Equal(SqlConnectionEncryptOption.Optional, settings.UserDb.UserComponents.Encrypt); + } + + [Theory] + [InlineData("absolutely")] + [InlineData("affirmative")] + [InlineData("never")] + [InlineData("always")] + [InlineData("none")] + [InlineData(" for sure ")] + public void ConnectionStringFromJsonThrowsException(string value) + { + ExecuteConnectionStringFromJsonThrowsException(value); + } + + [Fact] + public void SqlConnectionEncryptOptionConverterCanConvertFromTest() + { + // Get a converter + SqlConnectionEncryptOption option = SqlConnectionEncryptOption.Parse("false"); + TypeConverter converter = TypeDescriptor.GetConverter(option.GetType()); + // Use the converter to determine if can convert from string data type + Assert.True(converter.CanConvertFrom(null, typeof(string)), "Expecting to convert from a string type."); + // Use the same converter to determine if can convert from int or bool data types + Assert.False(converter.CanConvertFrom(null, typeof(int)), "Not expecting to convert from integer type."); + Assert.False(converter.CanConvertFrom(null, typeof(bool)), "Not expecting to convert from boolean type."); + } + + [Fact] + public void SqlConnectionEncryptOptionConverterCanConvertToTest() + { + // Get a converter + SqlConnectionEncryptOption option = SqlConnectionEncryptOption.Parse("false"); + TypeConverter converter = TypeDescriptor.GetConverter(option.GetType()); + // Use the converter to check if can convert from stirng + Assert.True(converter.CanConvertTo(null, typeof(string)), "Expecting to convert to a string type."); + // Use the same convert to check if can convert to int or bool + Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert from integer type."); + Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert from boolean type."); + } + + [Fact] + public void SqlConnectionEncryptOptionConverterConvertFromTest() + { + // Create a converter + SqlConnectionEncryptOption option = SqlConnectionEncryptOption.Parse("false"); + TypeConverter converter = TypeDescriptor.GetConverter(option.GetType()); + // Use the converter to convert all possible valid values + Assert.Equal(SqlConnectionEncryptOption.Parse("false"), converter.ConvertFrom("false")); + Assert.Equal(SqlConnectionEncryptOption.Parse("true"), converter.ConvertFrom("true")); + Assert.Equal(SqlConnectionEncryptOption.Parse("strict"), converter.ConvertFrom("strict")); + Assert.Equal(SqlConnectionEncryptOption.Parse("mandatory"), converter.ConvertFrom("mandatory")); + Assert.Equal(SqlConnectionEncryptOption.Parse("optional"), converter.ConvertFrom("optional")); + Assert.Equal(SqlConnectionEncryptOption.Parse("yes"), converter.ConvertFrom("yes")); + Assert.Equal(SqlConnectionEncryptOption.Parse("no"), converter.ConvertFrom("no")); + // Use the converter to covert invalid value + Assert.Throws(() => converter.ConvertFrom("affirmative")); + // Use the same converter to convert from bad data types + Assert.Throws(() => converter.ConvertFrom(1)); + Assert.Throws(() => converter.ConvertFrom(true)); + } + + [Fact] + public void SqlConnectionEncryptOptionConverterConvertToTest() + { + // Get a converter + SqlConnectionEncryptOption option = SqlConnectionEncryptOption.Parse("false"); + TypeConverter converter = TypeDescriptor.GetConverter(option.GetType()); + // Use the converter to convert all possible valid values to string + Assert.Equal("False", converter.ConvertTo(SqlConnectionEncryptOption.Parse("false"), typeof(string))); + Assert.Equal("True", converter.ConvertTo(SqlConnectionEncryptOption.Parse("true"), typeof(string))); + Assert.Equal("Strict", converter.ConvertTo(SqlConnectionEncryptOption.Parse("strict"), typeof(string))); + Assert.Equal("True", converter.ConvertTo(SqlConnectionEncryptOption.Parse("mandatory"), typeof(string))); + Assert.Equal("False", converter.ConvertTo(SqlConnectionEncryptOption.Parse("optional"), typeof(string))); + Assert.Equal("True", converter.ConvertTo(SqlConnectionEncryptOption.Parse("yes"), typeof(string))); + Assert.Equal("False", converter.ConvertTo(SqlConnectionEncryptOption.Parse("no"), typeof(string))); + // Use the same converter to try convert to bad data types + Assert.Throws(() => converter.ConvertTo(SqlConnectionEncryptOption.Parse("false"), typeof(int))); + Assert.Throws(() => converter.ConvertTo(SqlConnectionEncryptOption.Parse("false"), typeof(bool))); + } + + internal class UserDbConnectionStringSettings + { + [Required] + public UserSqlConnectionString UserDb { get; set; } + } + + internal class UserSqlConnectionString + { + public SqlConnectionStringBuilder UserComponents { get; set; } = new(); + + public override string ToString() + { + return UserComponents!.ConnectionString; + } + } + + internal static void ExecuteConnectionStringFromJsonThrowsException(string encryptOption) + { + var exception = Assert.Throws(() => LoadSettingsFromJsonStream(encryptOption)); + Assert.Contains("Failed to convert configuration", exception.Message, StringComparison.Ordinal); + } + + private static TSettings LoadSettingsFromJsonStream(string encryptOption) where TSettings : class + { + TSettings settingsOut = null; + + Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((ctx, configBuilder) => + { + // Note: Inside string interpolation, a { should be {{ and a } should be }} + // First, declare a stringified JSON + var json = $"{{ \"UserDb\": {{ \"UserComponents\": {{ \"NetworkLibrary\": \"DBMSSOCN\", \"UserID\": \"user\", \"Password\": \"password\", \"DataSource\": \"localhost\", \"InitialCatalog\": \"catalog\", \"Encrypt\": \"{encryptOption}\" }}}}}}"; + // Load the stringified JSON as a stream into the configuration builder + configBuilder.AddJsonStream(new MemoryStream(Encoding.ASCII.GetBytes(json))); + configBuilder.AddEnvironmentVariables(); + }) + .ConfigureServices((ctx, services) => + { + var configuration = ctx.Configuration; + services.AddOptions(); + services.Configure(ctx.Configuration); + settingsOut = configuration.Get(); + }) + .Build(); + + return settingsOut; + } + #endregion + internal void ExecuteConnectionStringTests(string connectionString) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); diff --git a/tools/props/Versions.props b/tools/props/Versions.props index b9a5cb84d7..ada3af2c63 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -73,6 +73,7 @@ 10.50.1600.1 0.13.2 6.0.0 + 6.0.0 $(NugetPackageVersion)