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 66791366f3..90367cf55a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -330,6 +330,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 4492274116..675dd57fe6 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 ecabdb9f04..997833437f 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 2bc842566a..b3a090f58f 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 @@ -468,6 +475,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); @@ -495,5 +655,6 @@ internal static void CheckEncryptType(SqlConnectionStringBuilder builder, SqlCon Assert.IsType(builder.Encrypt); Assert.Equal(expectedValue, builder.Encrypt); } + } } diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 2aab85d890..6588fb2651 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -74,6 +74,7 @@ 160.1000.6 0.13.2 6.0.0 + 6.0.0 $(NugetPackageVersion)