diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json new file mode 100644 index 0000000000..ffc7e87789 --- /dev/null +++ b/.config/CredScanSuppressions.json @@ -0,0 +1,25 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Program.cs", + "justification": "Test projects should be skipped" + }, + { + "file": "src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs", + "justification": "Test projects should be skipped" + }, + { + "file": "src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TdsServerCertificate.pfx", + "justification": "Test projects should be skipped" + }, + { + "file": "src/docker-compose.yml", + "justification": "Docker test project should be excluded" + }, + { + "file": "doc/samples/SqlConnectionStringBuilder.cs", + "justification": "Documentation could include sample data and can be ignored" + } + ] +} diff --git a/.config/PolicheckExclusions.xml b/.config/PolicheckExclusions.xml new file mode 100644 index 0000000000..d8c47d335d --- /dev/null +++ b/.config/PolicheckExclusions.xml @@ -0,0 +1,5 @@ + + SRC/MICROSOFT.DATA.SQLCLIENT/TESTS + .YML|.MD|.SQL + NOTICE.TXT + \ No newline at end of file diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json new file mode 100644 index 0000000000..077ef21c76 --- /dev/null +++ b/.config/tsaoptions.json @@ -0,0 +1,14 @@ +{ + "instanceUrl": "https://sqlclientdrivers.visualstudio.com/", + "projectName": "ADO.Net", + "areaPath": "ADO.Net", + "iterationPath": "ADO.Net\\TSA\\SqlClient", + "notificationAliases": [ "SqlClient@microsoft.com" ], + "repositoryName": "SqlClient", + "codebaseName": "SqlClient", + "allTools": true, + "template": "MSDATA_RevolutionR_Overloaded0", + "language": "csharp", + "includePathPatterns": "src/Microsoft.Data.SqlClient/*, src/Microsoft.SqlServer.Server/*, tools/*", + "excludePathPatterns": "src/Microsoft.Data.SqlClient/tests/*" +} diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 47cacc9fb7..8b2c950997 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -165,7 +165,6 @@ Manual Tests require the below setup to run: |TCPConnectionString | Connection String for a TCP enabled SQL Server instance. | `Server={servername};Database={Database_Name};Trusted_Connection=True;`
OR `Data Source={servername};Initial Catalog={Database_Name};Integrated Security=True;`| |NPConnectionString | Connection String for a Named Pipes enabled SQL Server instance.| `Server=\\{servername}\pipe\sql\query;Database={Database_Name};Trusted_Connection=True;`
OR
`Data Source=np:{servername};Initial Catalog={Database_Name};Integrated Security=True;`| |TCPConnectionStringHGSVBS | (Optional) Connection String for a TCP enabled SQL Server with Host Guardian Service (HGS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = HGS; Enclave Attestation Url = {AttestationURL};`| - |TCPConnectionStringAASVBS | (Optional) Connection String for a TCP enabled SQL Server with a VBS Enclave and using Microsoft Azure Attestation (AAS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = AAS; Enclave Attestation Url = {AttestationURL};`| |TCPConnectionStringNoneVBS | (Optional) Connection String for a TCP enabled SQL Server with a VBS Enclave and using None Attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = NONE;`| |TCPConnectionStringAASSGX | (Optional) Connection String for a TCP enabled SQL Server with a SGX Enclave and using Microsoft Azure Attestation (AAS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = AAS; Enclave Attestation Url = {AttestationURL};`| |EnclaveEnabled | Enables tests requiring an enclave-configured server.| @@ -176,8 +175,6 @@ Manual Tests require the below setup to run: |AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} | |AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` | |AzureKeyVaultTenantId | (Optional) The Azure Active Directory tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ | - |AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ | - |AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ | |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| |LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.| |LocalDbSharedInstanceName | (Optional) If LocalDB testing is supported and the instance is shared, this property configures the name of the shared instance of LocalDB to connect to. | Name of shared instance of LocalDB. | diff --git a/CHANGELOG.md b/CHANGELOG.md index 129ca46b22..674ab87826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,108 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +# Release Notes + +## [Stable release 5.1.7] - 2025-04-25 + +This update brings the following changes since the 5.1.6 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3285](https://github.com/dotnet/SqlClient/pull/3285)) +- Fixed inconsistencies between source and reference projects (PR [#3180](https://github.com/dotnet/SqlClient/pull/3180)) + +### Changed + +- Updated the following dependencies: + - [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/5.1.2) 5.1.1 to 5.1.2 for .NET Framework on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/5.1.2) 5.1.1 to 5.1.2 for .NET on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3068](https://github.com/dotnet/SqlClient/pull/3068)) + - [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive dependency on vulnerable [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.0) 6.0.0 (PR [#3207](https://github.com/dotnet/SqlClient/pull/3207)) + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3077](https://github.com/dotnet/SqlClient/pull/3077)) + - [System.Text.Encodings.Web](https://www.nuget.org/packages/System.Text.Encodings.Web/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive downgrade for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + - [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.11) 6.0.11 - Avoid transitive dependencies on older vulnerable versions for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + +## [Stable release 5.1.6] - 2024-08-27 + +### Fixed + +- Fixed Transient fault handling issue with `OpenAsync`. [#1983](https://github.com/dotnet/SqlClient/pull/1983) [#2508](https://github.com/dotnet/SqlClient/pull/2508) +- Fixed `AcquireTokenAsync` timeout handling for edge cases in `ActiveDirectoryAuthenticationProvider`. [#2706](https://github.com/dotnet/SqlClient/pull/2706) +- Fixed pending data with `SqlDataReader` against an encrypted column. [#2618](https://github.com/dotnet/SqlClient/pull/2618) [#2818](https://github.com/dotnet/SqlClient/pull/2818) + +### Changed + +- Upgraded `Azure.Identity` version from 1.11.3 to 1.11.4 [#2649] (https://github.com/dotnet/SqlClient/pull/2649) [#2529] (https://github.com/dotnet/SqlClient/pull/2529) to address [CVE-2024-35255](https://github.com/advisories/GHSA-m5vv-6r4h-3vj9). +- Upgraded `Microsoft.Identity.Client` version from 4.60.0 to 4.61.3 [#2649] (https://github.com/dotnet/SqlClient/pull/2649) [#2529] (https://github.com/dotnet/SqlClient/pull/2529) to address [CVE-2024-35255](https://github.com/advisories/GHSA-m5vv-6r4h-3vj9). +- Added caching to `TokenCredential` objects to take advantage of token caching. [#2776](https://github.com/dotnet/SqlClient/pull/2776) +- Code health improvements: [#2490] (https://github.com/dotnet/SqlClient/pull/2490) + +## [Stable release 5.1.5] - 2024-01-29 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed connection to unsubscribe from transaction completion events before returning it to the connection pool [#2321](https://github.com/dotnet/SqlClient/pull/2321) +- Fixed InvalidCastException when reading an Always Encrypted date or time column [#2324](https://github.com/dotnet/SqlClient/pull/2324) + +### Changed + +- Changed Microsoft.IdentityModel.JsonWebTokens and Microsoft.IdentityModel.Protocols.OpenIdConnect version 6.24.0 to 6.35.0 [#2320](https://github.com/dotnet/SqlClient/pull/2320) to address [CVE-2024-21319](https://www.cve.org/CVERecord?id=CVE-2024-21319) + +## [Stable release 5.1.4] - 2024-01-09 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed a deadlock problem for distributed transactions when on .NET. + +### Changed + +- Upgraded `Azure.Identity` dependency version to [1.10.3](https://www.nuget.org/packages/Azure.Identity/1.10.3) to address [CVE-2023-36414](https://github.com/advisories/GHSA-5mfx-4wcx-rv27). + +## [Stable release 5.1.3] - 2024-01-09 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed encryption downgrade issue. [CVE-2024-0056](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-0056) +- Fixed certificate chain validation logic flow. + +## [Stable release 5.1.2] - 2023-10-26 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed access violation when using SQL Express user instance. [#2101](https://github.com/dotnet/SqlClient/pull/2101) +- Fixed Always Encrypted secure enclave retry logic for async queries. [#1988](https://github.com/dotnet/SqlClient/pull/1988) +- Fixed LocalDb and managed SNI by improving the error messages and avoid falling back to the local service. [#2129](https://github.com/dotnet/SqlClient/pull/2129) +- Fixed .NET and .NET Standard file version. [2093](https://github.com/dotnet/SqlClient/pull/2093) +- Fixed non-string values and `SqlConnectionStringBuilder` property indexer issue. [#2018](https://github.com/dotnet/SqlClient/pull/2018) +- Fixed `SqlConnectionEncryptOption` type conversion by introducing the `SqlConnectionEncryptOptionConverter` attribute when using **appsettings.json** files. [#2057](https://github.com/dotnet/SqlClient/pull/2057) +- Fixed Transient fault handling issue with `OpenAsync`. [#1983](https://github.com/dotnet/SqlClient/pull/1983) +- Fixed activity correlator to continue use of same GUID for connection activity. [#1997](https://github.com/dotnet/SqlClient/pull/1997) + +### Changed + +- Updated `Microsoft.Data.SqlClient.SNI` (.NET Framework dependency) and `Microsoft.Data.SqlClient.SNI.runtime` (.NET Core/Standard dependency) version to `5.1.1`. [#2123](https://github.com/dotnet/SqlClient/pull/2123) + +## [Stable release 5.1.1] - 2023-03-28 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed an incorrect exception when a symmetric key fails to decrypt a column using Always Encrypted. [#1968](https://github.com/dotnet/SqlClient/pull/1968) +- Fixed `TransactionScope` connection issue when `Enlist` is `enabled`, `Pooling` is `disabled`, and `Network Connection Type` is set to `Redirect`. [#1967](https://github.com/dotnet/SqlClient/pull/1967) +- Fixed throttling of token requests by calling `AcquireTokenSilent`. [#1966](https://github.com/dotnet/SqlClient/pull/1966) +- Fixed TDS RPC error on large queries in `SqlCommand.ExecuteReaderAsync`. [#1965](https://github.com/dotnet/SqlClient/pull/1965) +- Fixed `NullReferenceException` in `GetBytesAsync`. [#1964](https://github.com/dotnet/SqlClient/pull/1964) + ## [Stable release 5.1.0] - 2023-01-19 This update brings the below changes over the previous release: diff --git a/src/NuGet.config b/NuGet.config similarity index 67% rename from src/NuGet.config rename to NuGet.config index 5832a9da27..4f39716bf4 100644 --- a/src/NuGet.config +++ b/NuGet.config @@ -4,4 +4,8 @@ + + + + diff --git a/build.proj b/build.proj index 1b3372bc95..1f295abc94 100644 --- a/build.proj +++ b/build.proj @@ -105,7 +105,7 @@ - $(DotNetCmd) dotnet build -c Release -p:ReferenceType=$(ReferenceType)" + $(DotNetCmd) dotnet build -c Release -p:ReferenceType=$(ReferenceType) diff --git a/doc/samples/SqlConnectionStringBuilder.cs b/doc/samples/SqlConnectionStringBuilder.cs index f1c3252880..d1d22254f1 100644 --- a/doc/samples/SqlConnectionStringBuilder.cs +++ b/doc/samples/SqlConnectionStringBuilder.cs @@ -21,12 +21,12 @@ static void Main() // connection string, and you can retrieve and // modify any of the elements. builder.ConnectionString = "server=(local);user id=ab;" + - "password= a!Pass113;initial catalog=AdventureWorks"; + "password=********;initial catalog=AdventureWorks"; // Now that the connection string has been parsed, // you can work with individual items. Console.WriteLine(builder.Password); - builder.Password = "new@1Password"; + builder.Password = "********"; // You can refer to connection keys using strings, // as well. When you use this technique (the default diff --git a/doc/samples/SqlConnectionStringBuilder3.cs b/doc/samples/SqlConnectionStringBuilder3.cs index 3e0c253039..fc691489fe 100644 --- a/doc/samples/SqlConnectionStringBuilder3.cs +++ b/doc/samples/SqlConnectionStringBuilder3.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Server=(local);Database=AdventureWorks;UID=ab;Pwd= a!Pass@@"; + "Server=(local);Database=AdventureWorks;UID=ab;Pwd=********"; Console.WriteLine("Original: " + connectString); SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); diff --git a/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs b/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs index 693298dd35..b2e6773559 100644 --- a/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs +++ b/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Data Source=(local);User ID=ab;Password=MyPassword;" + + "Data Source=(local);User ID=ab;Password=********;" + "Initial Catalog=AdventureWorks"; SqlConnectionStringBuilder builder = diff --git a/doc/samples/SqlConnectionStringBuilder_Remove.cs b/doc/samples/SqlConnectionStringBuilder_Remove.cs index 7de94386a1..835b8906c3 100644 --- a/doc/samples/SqlConnectionStringBuilder_Remove.cs +++ b/doc/samples/SqlConnectionStringBuilder_Remove.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Data Source=(local);User ID=ab;Password= a1Pass@@11;" + + "Data Source=(local);User ID=ab;Password=********;" + "Initial Catalog=AdventureWorks"; SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml index 261f5e57ed..dfbdbe8782 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionEnclaveProvider.xml @@ -20,8 +20,8 @@ the enclave attestation protocol as well as the logic for creating and caching e The information the provider uses to attest the enclave and generate a symmetric key for the session. The format of this information is specific to the enclave attestation protocol. A Diffie-Hellman algorithm object that encapsulates a client-side key pair. The set of parameters required for an enclave session. - The set of extra data needed for attestating the enclave. - The length of the extra data needed for attestating the enclave. + The set of extra data needed for attesting the enclave. + The length of the extra data needed for attesting the enclave. The requested enclave session or if the provider doesn't implement session caching. A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. @@ -29,8 +29,8 @@ the enclave attestation protocol as well as the logic for creating and caching e The endpoint of an attestation service for attesting the enclave. - A set of extra data needed for attestating the enclave. - The length of the extra data needed for attestating the enclave. + A set of extra data needed for attesting the enclave. + The length of the extra data needed for attesting the enclave. Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. The information SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. To be added. @@ -38,10 +38,11 @@ the enclave attestation protocol as well as the logic for creating and caching e The set of parameters required for enclave session. to indicate that a set of extra data needs to be generated for attestation; otherwise, . + Indicates if this is a retry from a failed call. When this method returns, the requested enclave session or if the provider doesn't implement session caching. This parameter is treated as uninitialized. A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. - A set of extra data needed for attestating the enclave. - The length of the extra data needed for attestating the enclave. + A set of extra data needed for attesting the enclave. + The length of the extra data needed for attesting the enclave. When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. If the enclave provider doesn't implement enclave session caching, this method is expected to return in the parameter. To be added. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 82f7aa8ec9..6bb436390f 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -839,7 +839,7 @@ Connections are considered the same if they have the same connection string. Dif The example displays the following text in the console window: ``` -Original: Data Source=(local);Initial Catalog=AdventureWorks;User ID=ab;Password= a1Pass@@11 +Original: Data Source=(local);Initial Catalog=AdventureWorks;User ID=ab;Password=******** Modified: Data Source=(local);Initial Catalog=AdventureWorks;Integrated Security=True Database = AdventureWorks ``` diff --git a/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml new file mode 100644 index 0000000000..43109253dc --- /dev/null +++ b/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml @@ -0,0 +1,72 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: symbolsFolder + type: string + default: symbols + + - name: softwareFolder + type: string + default: software + + - name: publishSymbols + type: boolean + +jobs: +- job: build_signed_akv_package + displayName: 'Build Signed AKV Provider Package' + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + + variables: + - template: ../../../libraries/variables.yml@self + - name: PublishSymbols + value: ${{ parameters['PublishSymbols'] }} + + steps: + - script: SET + displayName: 'Print Environment Variables' + + - template: ../steps/build-all-configurations-signed-dlls-step.yml@self + parameters: + product: AKV + nugetPackageRefVersion: $(MDS_PackageRef_Version) + AssemblyFileVersion: $(AKVAssemblyFileVersion) + + - template: ../steps/code-analyze-step.yml@self + parameters: + analyzeType: all + product: AKV + nugetPackageRefVersion: $(MDS_PackageRef_Version) + + - template: ../steps/esrp-code-signing-step.yml@self + parameters: + artifactType: dll + + - template: ../steps/generate-nuget-package-step.yml@self + parameters: + OutputDirectory: $(artifactDirectory) + nuspecPath: ${{variables.akvNuspecPath }} + NugetPackageVersion: ${{variables.AKVNuGetPackageVersion }} + referenceType: package + + - template: ../steps/esrp-code-signing-step.yml@self + parameters: + artifactType: pkg + + - template: ../steps/copy-dlls-for-test-step.yml@self + parameters: + product: AKV + referenceType: package + + # Publish symbols to servers + - template: ../steps/publish-symbols-step.yml@self + parameters: + referenceType: package + symbolsVersion: ${{variables.AKVNuGetPackageVersion }} + product: AKV + publishSymbols: ${{ parameters['PublishSymbols'] }} + symbolsArtifactName: akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(NuGetPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml new file mode 100644 index 0000000000..bfd392b186 --- /dev/null +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -0,0 +1,61 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: symbolsFolder + type: string + default: symbols + + - name: softwareFolder + type: string + default: software + + - name: publishSymbols + type: boolean + +jobs: +- job: build_signed_package + displayName: 'Build Signed MDS Package' + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + + variables: + - template: ../../../libraries/variables.yml@self + + steps: + - script: SET + displayName: 'Print Environment Variables' + + - powershell: | + Write-Host "##vso[task.setvariable variable=CDP_BUILD_TYPE_COPY;isOutput=true]$($env:CDP_BUILD_TYPE)" + name: GetBuildType + + - template: ../steps/build-all-configurations-signed-dlls-step.yml@self + + - template: ../steps/code-analyze-step.yml@self + parameters: + analyzeType: all + + - template: ../steps/esrp-code-signing-step.yml@self + parameters: + artifactType: dll + + - template: ../steps/generate-nuget-package-step.yml@self + parameters: + OutputDirectory: $(artifactDirectory) + + - template: ../steps/esrp-code-signing-step.yml@self + parameters: + artifactType: pkg + + - template: ../steps/copy-dlls-for-test-step.yml@self + parameters: + product: MDS + + # Publish symbols to servers + - template: ../steps/publish-symbols-step.yml@self + parameters: + publishSymbols: ${{ parameters['PublishSymbols'] }} + symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(NuGetPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml new file mode 100644 index 0000000000..a9fcd12720 --- /dev/null +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -0,0 +1,59 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: downloadPackageStep + type: step + default: + script: echo + + - name: packageFolderName + type: string + default: drop_build_build_signed_package + + - name: dependsOn + type: string + default: empty + +jobs: +- job: run_tests_package_reference + displayName: 'Run tests with package reference' + ${{ if ne(parameters.dependsOn, 'empty')}}: + dependsOn: '${{parameters.dependsOn }}' + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + isCustom: true + name: ADO-1ES-Pool + vmImage: 'ADO-MMS22-SQL19' + + variables: # More settings at https://aka.ms/obpipelines/yaml/jobs + - template: ../../../libraries/mds-validation-variables.yml@self + + steps: + - template: ../steps/pre-build-step.yml + + - ${{parameters.downloadPackageStep }} + + - template: ../steps/update-nuget-config-local-feed-step.yml + parameters: + downloadedNugetPath: $(Pipeline.Workspace)\${{parameters.packageFolderName }} + + - template: ../steps/update-config-file-step.yml + parameters: + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) + SupportsIntegratedSecurity: false + + - template: ../steps/prepare-test-db-step.yml + +# build & test + - template: ../steps/build-and-run-tests-netfx-step.yml + parameters: + referenceType: Package + + - template: ../steps/build-and-run-tests-netcore-step.yml + parameters: + referenceType: Package + cleanFirst: true diff --git a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml new file mode 100644 index 0000000000..1439aa1bb1 --- /dev/null +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -0,0 +1,337 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: downloadPackageStep + type: step + default: + script: echo + + - name: packageFolderName + type: string + default: drop_build_build_signed_package + + - name: dependsOn + type: string + default: '' + + - name: packageType + type: string + default: both + values: + - dll + - pdb + - both + + - name: assembly_file_version_netfx + type: string + default: $(AssemblyFileVersion) + + - name: assembly_file_version_core + type: string + default: $(AssemblyFileVersion) + + - name: current_netfx_target_framework + type: string + default: $(CurrentNetFxVersion) + +jobs: +- job: validate_signed_package + displayName: 'Verify signed package' + ${{ if ne(parameters.dependsOn, '')}}: + dependsOn: '${{parameters.dependsOn }}' + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + isCustom: true + name: ADO-1ES-Pool + vmImage: 'ADO-MMS22-SQL19' + + variables: # More settings at https://aka.ms/obpipelines/yaml/jobs + - template: ../../../libraries/mds-validation-variables.yml@self + + - name: pathToDownloadedNuget # path to the downloaded nuget files + value: $(Pipeline.Workspace)\${{parameters.packageFolderName }} + + - name: BuildType + value: $[ stageDependencies.buildMDS.build_signed_package.outputs['GetBuildType.CDP_BUILD_TYPE_COPY'] ] + + steps: + - script: SET + displayName: 'Print Environment Variables' + + - task: NuGetToolInstaller@1 + displayName: 'Use NuGet' + + - powershell: | + #Sets Variables for AssemblyFileVersion, AssemblyVersion and NugetPackageVersion + + [Xml] $versionprops = Get-Content -Path ".\tools\props\Versions.props" + Write-Host $versionprops.Project.PropertyGroup[0].AssemblyFileVersion + + $AssemblyVersion = $versionprops.Project.PropertyGroup[0].AssemblyVersion + + Write-Host "##vso[task.setvariable variable=ASSEMBLY_VERSION;]$AssemblyVersion" + displayName: 'Update assembly version property' + + - powershell: | + # Displays the paths of all the local cache directories + nuget locals all -List + + #Clears all files from all local cache directories + nuget locals all -Clear + displayName: 'Clear local cache' + + - ${{parameters.downloadPackageStep }} + + - powershell: | + # Install nuget package + Install-Package -Name "Microsoft.Data.SqlClient" -Destination "$(TempFolderName)" -Force -Source $(pathToDownloadedNuget) -SkipDependencies + + Write-Host "--------------------------------------------------" + Write-Host '$(TempFolderName)' + ls $(TempFolderName) + Write-Host "--------------------------------------------------" + displayName: 'Extract Nuget in temp folder' + + - powershell: | + # Artifact is stored in the Nuget folder + $packageType = '${{parameters.packageType}}' + + Write-Host "--------------------------------------------------" + Write-Host "This will verify the artifact signature" -ForegroundColor Green + Write-Host "--------------------------------------------------" + + nuget verify -All $(pathToDownloadedNuget)\*.nupkg + displayName: 'Verify nuget signature' + + - powershell: | + $buildType = [string]"$(BuildType)" + + if($buildType -eq 'Official') + { + # Recursively find all .dll files in TempFolder (installed nuget folder) + # Microsoft.Data.SqlClient.dll and Microsoft.Data.SqlClient.resources.dll (in localized folders) should have strong name + $dllFiles = Get-ChildItem -Path $(TempFolderName) -Recurse -Filter *.dll + $badDlls = @() + foreach ($file in $dllFiles) + { + # Run sn.k to verify the strong name on each dll + $result = & "C:\Program Files (x86)\Microsoft SDKs\Windows\*\bin\NETFX 4.8.1 Tools\sn.exe" -vf $file.FullName + Write-OutPut $result + + # if thhe dll is not valid, it would be delay signed or test-signed which is not meant for production + if($result[$result.Length-1] -notlike "* is valid") + { + $badDlls += $result[$result.Length-1] + } + } + if($badDlls.Count -gt 0) + { + Write-OutPut "Error: Invalid dlls are detected. Chek below list:" + foreach($dll in $badDlls) + { + Write-Output $dll + } + Exit -1 + } + Write-Host "Strong name has been verified for all dlls" + } + else + { + Write-OutPut "Strong name verification is not required for non-official builds" + } + displayName: 'Verify strong name is generated for production' + + - powershell: | + # Checks the expected folder names such as lib, ref, runtimes + Get-ChildItem -Path $(extractedNugetPath) -Directory | select Name | foreach { + if('$(expectedFolderNames)'.contains($_.Name)){ + Write-Host expected folder name verfied: $_.Name + } + } + displayName: 'Check expected folder names' + + - powershell: | + # Checks the version of DotNetFramework, NetStandard and NetCore + $countErr = 0 + $countPass = 0 + $excludNamesFromRuntimeFolder = 'lib','win','unix' + + Get-ChildItem -Path $(extractedNugetPath) -Directory | foreach { + $parentname=$_.Name + Write-Host $_.FullName -ForegroundColor yellow + + if($_.Name -ne 'runtimes') { + Get-ChildItem -Path $_.FullName -Directory | select Name | foreach { + if('$(expectedDotnetVersions)'.Contains($_.Name)){ + Write-Host "`tExpected version verified in $parentname": $_.Name -ForegroundColor green + $countPass += 1 + } + else{ + Write-Host "`tUnexpected version detected in $parentname": $_.Name + $countErr += 1 + } + } + } + + elseif ($_.Name -eq 'runtimes'){ + Get-ChildItem -Depth 3 -Path $_.FullName -Exclude $excludNamesFromRuntimeFolder -Directory | foreach{ + if('$(expectedDotnetVersions)'.Contains($_.Name)){ + Write-Host "`tExpected version verfied in $parentname": $_.Name + $countPass += 1 + } + else{ + Write-Host "`tUnexpected version detected": $_.Name -ForegroundColor Red + $countErr += 1 + } + } + } + else{ + Write-Host "`tUnknown folder " $_.Name -ForegroundColor Red + Exit -1 + } + } + + Write-Host "_______________" + Write-Host "Expected: $countPass" + Write-Host "Unexpected: $countErr" + Write-Host "_______________" + if ($countErr -ne 0) + { + Write-Host "Unexpected versions are detected!" -ForegroundColor Red + Exit -1 + } + displayName: 'Check Expected framework' + + - powershell: | + # list all the child items of created temp folder + + #Verify all DLLs unzipped match "expected" hierarchy + + foreach( $folderName in (Get-ChildItem -Path $(extractedNugetPath) -Directory).Name) + { + # List all Childerns of the Path + Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File + $subFiles = Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File + + foreach($file in $subFiles) + { + if($subFiles[0].Name -like "*.dll" -and $subFiles[1].Name -like "*.pdb" ) + { + Write-Host $subFiles[0].Name -ForegroundColor Green + Write-Host $subFiles[1].Name -ForegroundColor Green + if(($folderName -eq 'lib') -or ($folderName -eq 'ref')) + { + if($subFiles[2].Name -like "*.xml") + { + Write-Host $subFiles[2].Name -ForegroundColor Green + } + else + { + $subFiles[2].Name + Write-Host "Expected file pattern did not match to *.xml" -ForegroundColor Red + Exit -1 + } + } + } + else + { + $subFiles[0].Name + $subFiles[1].Name + Write-Host "Expected file pattern did not match to *.dll, *.pdb" -ForegroundColor Red + Exit -1 + } + } + } + displayName: 'Verify all DLLs unzipped match "expected" hierarchy' + - powershell: | + # Verify all dlls status are Valid + + $dlls = Get-ChildItem -Path $(extractedNugetPath) -Recurse -Include *.dll + foreach ($status in $dlls | Get-AuthenticodeSignature) + { + if ($status.Status -eq "Valid") + { + Write-Host $status.Status $status.Path + } + else + { + Write-Host "dll status of '$status.Path' is not valid!" -ForegroundColor Red + $status + Exit -1 + } + } + displayName: 'Verify all dlls status are Valid' + + - powershell: | + # This will check for ProductVersion and FileVersion + # For NetFx we have a different FileVersion, but product versions are all the same some may have and extra numbering at the end. we only check for # first parts + + foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) + { + if ($pVersion.ProductVersion -Like '$(ProductVersion)*') + { + Write-Host Valid Product Version:"$pVersion.ProductVersion" $pVersion.ProductVersion detected for $pVersion.FileName -ForegroundColor Green + } + else + { + Write-Host "Wrong ProductVersion detected. Expected: '$(ProductVersion)', but Detected: "$pVersion.ProductVersion"" + Exit -1 + } + + if($pVersion.FileName -like '*lib\${{parameters.current_netfx_target_framework }}*'){ + + if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_netfx }}') + { + Write-Host 'Correct File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Green + } + else + { + Write-Host 'Wrong File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Red + Exit -1 + } + } + else + { + + if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_core}}') + { + Write-Host 'Correct File version Detected for netcore and netstandard,' $pVersion.FileVersion -ForegroundColor Green + } + else + { + Write-Host 'Wrong File version Detected for netcore and netstandard and ref folder of netfx,' $pVersion.FileVersion -ForegroundColor Red + Exit -1 + } + } + } + + Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo + displayName: 'Verify "File Version" matches provided pipeline variable "ASSEMBLY_FILE_VERSION" for DLLs' + + - powershell: | + #Change TestMicrosoftDataSqlClientVersion + + [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" + $versionpropspath = "tools\props\Versions.props" + $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(NugetPackageVersion)" + Write-Host "Saving Test nuget version at $rootfolder\props ...." -ForegroundColor Green + $versionprops.Save($versionpropspath) + + displayName: 'Modify TestMicrosoftDataSqlClientVersion' + + - powershell: | + #Change TestMicrosoftDataSqlClientVersion + + [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" + $AssemblyFileVersion = $versionprops.Project.PropertyGroup[0].AssemblyFileVersion + $AssemblyVersion = $versionprops.Project.PropertyGroup[0].AssemblyVersion + + if($AssemblyFileVersion -eq $AssemblyVersion) + { + Write-Host AssemblyFileVersion: $AssemblyFileVersion should not be equal to: $AssemblyVersion + Exit -1 + } + displayName: 'Check "AssemblyFileVersion" is not same as "AssemblyVersion" in version.props' diff --git a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml new file mode 100644 index 0000000000..36bc57c7da --- /dev/null +++ b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml @@ -0,0 +1,61 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: AssemblyFileVersion + type: string + default: $(AssemblyFileVersion) + + - name: Configuration + type: string + default: '$(Configuration)' + + - name: nugetPackageRefVersion + type: string + default: '' + + - name: product + default: MDS + values: + - MDS + - AKV + - MSS + +steps: +- task: DownloadSecureFile@1 + displayName: 'Download Key Pair' + inputs: + secureFile: netfxKeypair.snk + retryCount: 5 + +- ${{ if eq(parameters.product, 'MDS') }}: + - task: MSBuild@1 + displayName: 'BuildAllConfigurations using build.proj' + inputs: + solution: '**/build.proj' + configuration: '${{parameters.Configuration }}' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SignAssembly=true -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + +- ${{ if eq(parameters.product, 'AKV') }}: + - task: MSBuild@1 + displayName: 'BuildAKVNetStAllOS using build.proj' + inputs: + solution: '**/build.proj' + configuration: '$(Configuration)' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAKVNetStAllOS -p:NugetPackageVersion=${{parameters.nugetPackageRefVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + + - task: MSBuild@1 + displayName: 'BuildAKVNetFx using build.proj' + inputs: + solution: '**/build.proj' + configuration: '$(Configuration)' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAKVNetFx -p:NugetPackageVersion=${{parameters.nugetPackageRefVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + + - task: MSBuild@1 + displayName: 'BuildAKVNetCoreAllOS using build.proj' + inputs: + solution: '**/build.proj' + configuration: '$(Configuration)' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAKVNetCoreAllOS -p:NugetPackageVersion=${{parameters.nugetPackageRefVersion }} -p:ReferenceType=Package -p:SignAssembly=true -p:AssemblyOriginatorKeyFile=$(Agent.TempDirectory)\netfxKeypair.snk' diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml new file mode 100644 index 0000000000..f747fa57e6 --- /dev/null +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml @@ -0,0 +1,80 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: TargetNetCoreVersion + type: string + default: $(TargetNetCoreVersion) + + - name: configuration + type: string + default: $(Configuration) + + - name: referenceType + default: Project + values: + - Project + - Package + + - name: NugetPackageVersion + type: string + default: $(NugetPackageVersion) + + - name: platform + type: string + default: $(Platform) + + - name: cleanFirst + type: boolean + default: false + + - name: TestTargetOS + type: string + default: Windowsnetcoreapp + values: + - Windowsnetfx + - Windowsnetcoreapp + - Unixnetcoreapp + + - name: retryCountOnManualTests + type: number + default: 2 + +steps: +- ${{ if eq(parameters.cleanFirst, true)}}: + - task: MSBuild@1 + displayName: 'Clean artifacts folder' + inputs: + solution: build.proj + msbuildArguments: '-t:clean' + +- task: MSBuild@1 + displayName: 'Build AKV Provider .NET' + inputs: + solution: build.proj + msbuildArchitecture: x64 + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} ' + +- task: MSBuild@1 + displayName: 'MSBuild Build Tests for ${{parameters.TargetNetCoreVersion }}' + inputs: + solution: build.proj + msbuildArchitecture: x64 + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:Configuration=${{parameters.configuration }}' + +- task: DotNetCoreCLI@2 + displayName: 'Run Functional Tests for ${{parameters.TargetNetCoreVersion }}' + inputs: + command: test + projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' + +- task: DotNetCoreCLI@2 + displayName: 'Run Manual Tests for ${{parameters.TargetNetCoreVersion }}' + inputs: + command: test + projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' + retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml new file mode 100644 index 0000000000..ab77af3ee9 --- /dev/null +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml @@ -0,0 +1,79 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: TargetNetFxVersion + type: string + default: $(TargetNetFxVersion) + + - name: configuration + type: string + default: $(Configuration) + + - name: referenceType + default: Project + values: + - Project + - Package + + - name: NugetPackageVersion + type: string + default: $(NugetPackageVersion) + + - name: platform + type: string + default: $(Platform) + + - name: cleanFirst + type: boolean + default: false + + - name: TestTargetOS + type: string + default: Windowsnetfx + values: + - Windowsnetfx + - Windowsnetcoreapp + - Unixnetcoreapp + + - name: retryCountOnManualTests + type: number + default: 2 + +steps: +- ${{ if eq(parameters.cleanFirst, true)}}: + - task: MSBuild@1 + displayName: 'Clean artifacts folder' + inputs: + solution: build.proj + msbuildArguments: '-t:clean' + +- task: MSBuild@1 + displayName: 'Build AKV Provider .NET Framework' + inputs: + solution: build.proj + msbuildArchitecture: x64 + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} ' + +- task: MSBuild@1 + displayName: 'MSBuild Build Tests for ${{parameters.TargetNetFxVersion }}' + inputs: + solution: build.proj + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' + +- task: DotNetCoreCLI@2 + displayName: 'Run Functional Tests for ${{parameters.TargetNetFxVersion }}' + inputs: + command: test + projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + +- task: DotNetCoreCLI@2 + displayName: 'Run Manual Tests for ${{parameters.TargetNetFxVersion }}' + inputs: + command: test + projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/code-analyze-step.yml b/eng/pipelines/common/templates/steps/code-analyze-step.yml new file mode 100644 index 0000000000..918bc273fa --- /dev/null +++ b/eng/pipelines/common/templates/steps/code-analyze-step.yml @@ -0,0 +1,51 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: analyzeType + values: + - roslyn + - inspect + - all + + - name: sourceRoot + type: string + default: $(REPOROOT) + + - name: nugetPackageRefVersion + type: string + default: '' + + - name: product + default: MDS + values: + - MDS + - AKV + - MSS + +steps: +- ${{ if or(eq(parameters.analyzeType, 'roslyn'), eq(parameters.analyzeType, 'all')) }}: + - ${{ if eq(parameters.product, 'MDS') }}: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 + displayName: 'Guardian Dotnet Analyzers ' + inputs: + msBuildVersion: 17.0 + msBuildArchitecture: x64 + setupCommandlinePicker: vs2022 + msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + - ${{ if eq(parameters.product, 'AKV') }}: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 + displayName: 'Guardian Dotnet Analyzers ' + inputs: + msBuildVersion: 17.0 + msBuildArchitecture: x64 + setupCommandlinePicker: vs2022 + msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:NugetPackageVersion=${{parameters.nugetPackageRefVersion }} -p:ReferenceType=Package -t:BuildAKVNetCoreAllOS -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' + +- ${{ if or(eq(parameters.analyzeType, 'inspect'), eq(parameters.analyzeType, 'all')) }}: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-codeinspector.CodeInspector@2 + displayName: 'Run Code Inspector' + inputs: + LogLevel: Error diff --git a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml new file mode 100644 index 0000000000..ec1ac1f94f --- /dev/null +++ b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml @@ -0,0 +1,107 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: Configuration + type: string + default: '$(Configuration)' + + - name: symbolsFolder + type: string + default: symbols + + - name: softwareFolder + type: string + default: software + + - name: referenceType + default: project + values: + - project + - package + + - name: listOfTF + type: object + default: + - net462 + - net6.0 + - netstandard2.0 + - netstandard2.1 + + - name: product + default: MDS + values: + - MDS + - AKV + - MSS + +steps: +- powershell: | + $software = '${{parameters.softwareFolder}}' + $symbols = '${{parameters.symbolsFolder}}' + + md $software + md $software\win + + md $symbols + md $symbols\win + displayName: 'Make base directories' + +- ${{ each targetFramework in parameters.listOfTF }}: + - ${{ if eq(parameters.product, 'MDS') }}: + - powershell: | + $software = '${{parameters.softwareFolder}}' + $tf = '${{ targetFramework }}' + md $software\win\$tf + + if ($tf.StartsWith('net4')) + { + Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\Microsoft.Data.SqlClient\netfx\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse + } + else + { + Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse + } + + $symbols = '${{parameters.symbolsFolder}}' + md $symbols\win\$tf + + if ($tf.StartsWith('net4')) + { + Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse + } + else + { + Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse + } + + Write-Host "Artifacts fetched for testing" + Get-Location + displayName: 'Prepare ${{ targetFramework }} Arifacts for Testing' + + - ${{ if eq(parameters.product, 'AKV') }}: + - powershell: | + $software = '${{parameters.softwareFolder}}' + $tf = '${{ targetFramework }}' + md $software\win\$tf + + Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\AzureKeyVaultProvider\$tf\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.dll" "$software\win\$tf" -recurse + + $symbols = '${{parameters.symbolsFolder}}' + md $symbols\win\$tf + + Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\AzureKeyVaultProvider\$tf\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb" "$symbols\win\$tf" -recurse + + Write-Host "Artifacts fetched for testing" + Get-Location + displayName: 'Prepare ${{ targetFramework }} Arifacts for Testing' + +- powershell: | + $software = '${{parameters.softwareFolder}}' + $symbols = '${{parameters.symbolsFolder}}' + + Get-ChildItem -recurse "$software\*.dll" + Get-ChildItem -recurse "$symbols\*.pdb" + displayName: 'List the prepared files' diff --git a/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml b/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml new file mode 100644 index 0000000000..b1e14aa604 --- /dev/null +++ b/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml @@ -0,0 +1,153 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: artifactType + values: + - dll + - pkg + + - name: sourceRoot + type: string + default: $(REPOROOT) + + - name: artifactDirectory + type: string + default: $(artifactDirectory) + + - name: ESRPConnectedServiceName + type: string + default: $(ESRPConnectedServiceName) + + - name: appRegistrationClientId + type: string + default: $(appRegistrationClientId) + + - name: appRegistrationTenantId + type: string + default: $(appRegistrationTenantId) + + - name: AuthAKVName + type: string + default: $(AuthAKVName) + + - name: AuthSignCertName + type: string + default: $(AuthSignCertName) + + - name: EsrpClientId + type: string + default: $(EsrpClientId) + +steps: +- ${{ if eq(parameters.artifactType, 'dll') }}: + - task: EsrpMalwareScanning@5 + displayName: 'ESRP MalwareScanning' + inputs: + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' + AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + FolderPath: '${{parameters.sourceRoot }}' + Pattern: '*.dll' + CleanupTempStorage: 1 + VerboseLogin: 1 + - task: EsrpCodeSigning@5 + displayName: 'ESRP CodeSigning' + inputs: + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' + AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + AuthAKVName: '${{parameters.AuthAKVName }}' + AuthSignCertName: '${{parameters.AuthSignCertName }}' + FolderPath: '${{parameters.sourceRoot }}' + Pattern: '*.dll' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolSign", + "parameters": [ + { + "parameterName": "OpusName", + "parameterValue": "Microsoft Data SqlClient Data Provider for SQL Server" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "http://www.microsoft.com" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + +- ${{ if eq(parameters.artifactType, 'pkg') }}: + - task: EsrpMalwareScanning@5 + displayName: 'ESRP MalwareScanning Nuget Package' + inputs: + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' + AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + FolderPath: '${{parameters.artifactDirectory }}' + Pattern: '*.nupkg' + CleanupTempStorage: 1 + VerboseLogin: 1 + - task: EsrpCodeSigning@5 + displayName: 'ESRP CodeSigning Nuget Package' + inputs: + inputs: + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' + AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + AuthAKVName: '${{parameters.AuthAKVName }}' + AuthSignCertName: '${{parameters.AuthSignCertName }}' + FolderPath: '${{parameters.artifactDirectory }}' + Pattern: '*.nupkg' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] diff --git a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml new file mode 100644 index 0000000000..34e3544ce6 --- /dev/null +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -0,0 +1,42 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: nuspecPath + type: string + default: '$(nuspecPath)' + + - name: NugetPackageVersion + type: string + default: '$(NugetPackageVersion)' + + - name: OutputDirectory + type: string + default: '$(Build.SourcesDirectory)/packages' + + - name: Configuration + type: string + default: '$(Configuration)' + + - name: referenceType + default: project + values: + - project + - package + +steps: +- task: NuGetToolInstaller@1 + displayName: 'Install Latest Nuget' + inputs: + checkLatest: true +- powershell: | + $Commit=git rev-parse HEAD + Write-Host "##vso[task.setvariable variable=CommitHead;]$Commit" + displayName: CommitHead +- task: NuGetCommand@2 + displayName: 'NuGet pack' + inputs: + command: custom + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' diff --git a/eng/pipelines/common/templates/steps/pre-build-step.yml b/eng/pipelines/common/templates/steps/pre-build-step.yml new file mode 100644 index 0000000000..327b5f21a5 --- /dev/null +++ b/eng/pipelines/common/templates/steps/pre-build-step.yml @@ -0,0 +1,20 @@ +################################################################################# +# 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. # +################################################################################# +steps: +- script: SET + displayName: 'Print Environment Variables' + +- powershell: | + # use sqlcmd to try to connect to localdb + $svc_name = "SQLBrowser" + Get-Service $svc_name | Select-Object -Property Name, StartType, Status + Set-Service -StartupType Automatic $svc_name + net start $svc_name + Get-Service $svc_name | Select-Object -Property Name, StartType, Status + displayName: 'Start SQLBrowser' + +- task: NuGetToolInstaller@1 + displayName: 'Use NuGet ' diff --git a/eng/pipelines/common/templates/steps/prepare-test-db-step.yml b/eng/pipelines/common/templates/steps/prepare-test-db-step.yml new file mode 100644 index 0000000000..8597a0c9e5 --- /dev/null +++ b/eng/pipelines/common/templates/steps/prepare-test-db-step.yml @@ -0,0 +1,26 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: databaseName + type: string + default: $(Database) + + - name: targetFramework + type: string + default: net6.0 + +steps: +- task: DotNetCoreCLI@2 + displayName: 'Build Ext Utilities' + inputs: + arguments: '-f ${{parameters.targetFramework }}' + workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities +- task: DotNetCoreCLI@2 + displayName: 'Create Test Database' + inputs: + command: run + arguments: '-f ${{parameters.targetFramework }} -- "CreateDatabase" ${{parameters.databaseName }} ' + workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml new file mode 100644 index 0000000000..cd1954ce77 --- /dev/null +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -0,0 +1,150 @@ +#################################################################################### +# 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. # +# # +# doc: https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL # +#################################################################################### +parameters: + - name: SymAccount + type: string + default: 'SqlClientDrivers' + + - name: publishSymbols + type: string + default: '$(PublishSymbols)' + + - name: symbolsVersion + type: string + default: '$(NuGetPackageVersion)' + + - name: symbolServer + type: string + default: '$(SymbolServer)' + + - name: symbolTokenUri + type: string + default: '$(SymbolTokenUri)' + + - name: symbolsArtifactName + type: string + + - name: publishToServers + type: object + default: + internal: true + public: true + + - name: referenceType + default: project + values: + - project + - package + + - name: product + default: MDS + values: + - MDS + - AKV + - MSS + +steps: +- powershell: 'Write-Host "##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{parameters.SymAccount}}"' + displayName: 'Update Symbol.AccountName with ${{parameters.SymAccount}}' + condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) + +- ${{ if eq(parameters.product, 'MDS') }}: + - task: PublishSymbols@2 + displayName: 'Upload symbols to ${{parameters.SymAccount }} org' + inputs: + SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' + SearchPattern: | + Windows_NT/$(Configuration).AnyCPU/**/Microsoft.Data.SqlClient.pdb + Unix/$(Configuration).AnyCPU/**/Microsoft.Data.SqlClient.pdb + IndexSources: false + SymbolServerType: TeamServices + SymbolsMaximumWaitTime: 60 + SymbolExpirationInDays: 1825 # 5 years + SymbolsProduct: Microsoft.Data.SqlClient + SymbolsVersion: ${{parameters.symbolsVersion }} + SymbolsArtifactName: ${{parameters.symbolsArtifactName }} + Pat: $(System.AccessToken) + condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) + +- ${{ if eq(parameters.product, 'AKV') }}: + - task: PublishSymbols@2 + displayName: 'Upload symbols to ${{parameters.SymAccount }} org' + inputs: + SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' + SearchPattern: | + Windows_NT/$(Configuration).AnyCPU/AzureKeyVaultProvider/**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb + AnyOS/$(Configuration).AnyCPU/AzureKeyVaultProvider/**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb + IndexSources: false + SymbolServerType: TeamServices + SymbolsMaximumWaitTime: 60 + SymbolExpirationInDays: 1825 # 5 years + SymbolsProduct: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + SymbolsVersion: ${{parameters.symbolsVersion }} + SymbolsArtifactName: ${{parameters.symbolsArtifactName }} + Pat: $(System.AccessToken) + condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) + +- task: AzureCLI@2 + displayName: 'Publish symbols' + condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) + inputs: + azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' + scriptType: ps + scriptLocation: inlineScript + inlineScript: | + $publishToInternalServer = "${{parameters.publishToServers.internal }}".ToLower() + $publishToPublicServer = "${{parameters.publishToServers.public }}".ToLower() + + echo "Publishing request name: ${{parameters.symbolsArtifactName }}" + echo "Publish to internal server: $publishToInternalServer" + echo "Publish to public server: $publishToPublicServer" + + $symbolServer = "${{parameters.symbolServer }}" + $tokenUri = "${{parameters.symbolTokenUri }}" + # Registered project name in the symbol publishing pipeline: https://portal.microsofticm.com/imp/v3/incidents/incident/520844254/summary + $projectName = "Microsoft.Data.SqlClient.SNI" + + # Get the access token for the symbol publishing service + $symbolPublishingToken = az account get-access-token --resource $tokenUri --query accessToken -o tsv + + echo "> 1.Symbol publishing token acquired." + + echo "Registering the request name ..." + $requestName = "${{parameters.symbolsArtifactName }}" + $requestNameRegistrationBody = "{'requestName': '$requestName'}" + Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $requestNameRegistrationBody + + echo "> 2.Registration of request name succeeded." + + echo "Publishing the symbols ..." + $publishSymbolsBody = "{'publishToInternalServer': $publishToInternalServer, 'publishToPublicServer': $publishToPublicServer}" + echo "Publishing symbols request body: $publishSymbolsBody" + Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $publishSymbolsBody + + echo "> 3.Request to publish symbols succeeded." + + # The following REST calls are used to check publishing status. + echo "> 4.Checking the status of the request ..." + + Invoke-RestMethod -Method GET -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" + + echo "Use below tables to interpret the values of xxxServerStatus and xxxServerResult fields from the response." + + echo "PublishingStatus" + echo "-----------------" + echo "0 NotRequested; The request has not been requested to publish." + echo "1 Submitted; The request is submitted to be published" + echo "2 Processing; The request is still being processed" + echo "3 Completed; The request has been completed processing. It can be failed or successful. Check PublishingResult to get more details" + + echo "PublishingResult" + echo "-----------------" + echo "0 Pending; The request has not completed or has not been requested." + echo "1 Succeeded; The request has published successfully" + echo "2 Failed; The request has failed to publish" + echo "3 Cancelled; The request was cancelled" diff --git a/eng/pipelines/common/templates/steps/update-config-file-step.yml b/eng/pipelines/common/templates/steps/update-config-file-step.yml new file mode 100644 index 0000000000..2530f35d23 --- /dev/null +++ b/eng/pipelines/common/templates/steps/update-config-file-step.yml @@ -0,0 +1,35 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: TCPConnectionString + type: string + default: '' + + - name: NPConnectionString + type: string + default: '' + + - name: SupportsIntegratedSecurity + type: boolean + default: false + +steps: +# All properties should be added here, and this template should be used for any manipulation of the config.json file. +- powershell: | + $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + foreach ($p in $jdata) + { + if ("${{parameters.TCPConnectionString }}" -ne ""){ + $p.TCPConnectionString="${{parameters.TCPConnectionString }}"} + + if ("${{parameters.NPConnectionString }}" -ne ""){ + $p.NPConnectionString="${{parameters.NPConnectionString }}"} + + $p.SupportsIntegratedSecurity=[System.Convert]::ToBoolean("${{parameters.SupportsIntegratedSecurity }}") + } + $jdata | ConvertTo-Json | Set-Content "config.json" + workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities + displayName: 'Update config.json' diff --git a/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml new file mode 100644 index 0000000000..5258a3941d --- /dev/null +++ b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml @@ -0,0 +1,78 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: downloadedNugetPath # path to the downloaded nuget files + type: string + + - name: nugetPackageVersion + type: string + default: $(NugetPackageVersion) + +steps: +- powershell: | + # Get a list of package sources available + Get-PackageSource + + #Current location + Get-Location + + # Register the local nuget folder to be used by nuget.config + Register-PackageSource -Name "Package Source" -Location ${{parameters.downloadedNugetPath }} -Force -ProviderName NuGet -Trusted + + # Get a list of package sources available after the change + Get-PackageSource + + #Set the Nuget.config file in the project to use extracted package + $rootFolder = Get-location + [Xml] $nugetConfig = Get-Content -Path "Nuget.config" + $Value = Resolve-Path ${{parameters.downloadedNugetPath }} + $newAdd = $nugetConfig.CreateElement("add") + $newAdd.SetAttribute("key","Package source") + $newAdd.SetAttribute("value", "$Value\" ) + $nugetConfig.configuration.packageSources.AppendChild($newAdd) + $nugetConfig.Save("$rootFolder\Nuget.config") + displayName: 'Update NuGet config file to read from Nuget folder' + +- task: MSBuild@1 + displayName: 'Restore nugets' + inputs: + solution: build.proj + msbuildArchitecture: x64 + msbuildArguments: '-t:restore' + +- powershell: | + $Doc = [xml](Get-Content ".\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj") + $parent_xpath = '/Project/ItemGroup/ProjectReference' + $node = $Doc.SelectSingleNode($parent_xpath) + $parentNode = $node.ParentNode + while($node -ne $null) { + $node.ParentNode.RemoveChild($node) + $node = $Doc.SelectSingleNode($parent_xpath) + } + + $parent_xpath = '/Project/ItemGroup/PackageReference[@Include="Microsoft.Data.SqlClient"]' + $node = $Doc.SelectSingleNode($parent_xpath) + + if($node -ne $null){ + $node.Version="${{parameters.nugetPackageVersion }}" + } + else{ + $packagerefnode = $doc.createelement("packagereference") + $value = $doc.selectsinglenode('/project/itemgroup/projectreference') + $attrinclude = $doc.createattribute("include") + $attrinclude.value = "microsoft.data.sqlclient" + $attrversion = $doc.createattribute("version") + $attrversion.value = "${{parameters.nugetPackageVersion }}" + $packagerefnode.attributes.append($attrinclude) + $packagerefnode.attributes.append($attrversion) + $parentNode.AppendChild($packageRefNode) + } + + $currentFolder = Get-Location + $filePath = Join-Path $currentFolder "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj" + $Doc.Save($filePath) + workingDirectory: 'src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider' + displayName: 'Update AKV Project Ref to Package Ref (.NET Framework/Core)' diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml new file mode 100644 index 0000000000..299a4085fa --- /dev/null +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -0,0 +1,179 @@ +################################################################################# +# 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. # +################################################################################# + +name: $(Year:YY)$(DayOfYear)$(Rev:.rr) +trigger: + branches: + include: + - internal/release/5.1 + paths: + include: + - src + - eng + - tools + - .config + - build.proj + - '*.cmd' + - '*.sh' + +schedules: +- cron: '30 23 * * Sun' + displayName: Weekly Sunday 4:30 PM (UTC - 7) Build + branches: + include: + - internal/release/5.1 + always: true + +- cron: '30 3 * * Mon-Fri' + displayName: Mon-Fri 8:30 PM (UTC - 7) Build + branches: + include: + - internal/release/5.1 + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + +- name: publishSymbols + type: boolean + default: false + +- name: MDS_PackageRef_Version + displayName: 'MDS package version of AKV Provider (build AKV)' + type: string + default: 5.1.2 + +- name: CurrentNetFxVersion + displayName: 'Lowest supported .NET Framework version (MDS validation)' + type: string + default: 'net462' + +- name: enableAllSdlTools + displayName: 'Enable all SDL tools' + type: boolean + default: true + +- name: oneBranchType + displayName: 'Select OneBranch template' + default: Official + values: + - NonOfficial + - Official + +variables: + - template: /eng/pipelines/libraries/variables.yml@self + - name: packageFolderName + value: drop_buildMDS_build_signed_package + - name: PublishSymbols + value: ${{ parameters['publishSymbols'] }} + - name: MDS_PackageRef_Version + value: ${{ parameters['MDS_PackageRef_Version'] }} + - name: CurrentNetFxVersion + value: ${{ parameters['CurrentNetFxVersion'] }} + - name: ProductVersion #MDS product version (MDS validation) + value: $(NUGETPACKAGEVERSION) + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.${{parameters.oneBranchType }}.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates + parameters: + featureFlags: + WindowsHostVersion: 1ESWindows2022 + globalSdl: # https://aka.ms/obpipelines/sdl + tsa: + # The OneBranch template will set 'break' to false for the other SDL + # tools when TSA is enabled. This allows TSA to gather the results + # and publish them for downstream analysis. + enabled: ${{parameters.enableAllSdlTools }} + apiscan: + enabled: ${{parameters.enableAllSdlTools }} + # For non-official builds, the OneBranch template seems to set APIScan's + # 'break' to true even when TSA is enabled. We don't want APIScan to + # break non-official builds, so we explicitly set 'break' to false here. + ${{ if ne(parameters.oneBranchType, 'Official') }}: + break: false + softwareFolder: $(softwareFolder) + symbolsFolder: $(symbolsFolder) + softwarename: Microsoft.Data.SqlClient + versionNumber: $(AssemblyFileVersion) + codeql: + compiled: + enabled: false #[warning]Consider running CodeQL on the default branch only. + sbom: + enabled: ${{parameters.enableAllSdlTools }} + packageName: Microsoft.Data.SqlClient + packageVersion: $(NugetPackageVersion) + policheck: + enabled: ${{parameters.enableAllSdlTools }} + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + exclusionsFile: $(REPOROOT)\.config\PolicheckExclusions.xml + asyncSdl: + enabled: false + credscan: + enabled: ${{parameters.enableAllSdlTools }} + suppressionsFile: $(REPOROOT)/.config/CredScanSuppressions.json + binskim: + enabled: ${{parameters.enableAllSdlTools }} + armory: + enabled: ${{parameters.enableAllSdlTools }} + break: true + eslint: # TypeScript and JavaScript + enabled: false + roslyn: + enabled: ${{parameters.enableAllSdlTools }} + break: true + publishLogs: + enabled: ${{parameters.enableAllSdlTools }} + tsaOptionsPath: $(REPOROOT)\.config\tsaoptions.json + disableLegacyManifest: true + stages: + - stage: buildAKV + displayName: 'Build AKV Provider' + jobs: + - template: eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml@self + parameters: + symbolsFolder: $(symbolsFolder) + softwareFolder: $(softwareFolder) + publishSymbols: ${{ parameters['publishSymbols'] }} + + - stage: buildMDS + displayName: 'Build MDS' + jobs: + - template: eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self + parameters: + symbolsFolder: $(symbolsFolder) + softwareFolder: $(softwareFolder) + publishSymbols: ${{ parameters['publishSymbols'] }} + + - stage: mds_package_validation + displayName: 'MDS Package Validation' + dependsOn: buildMDS + jobs: + - template: eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self + parameters: + packageFolderName: $(packageFolderName) + downloadPackageStep: + download: current + artifact: $(packageFolderName) + patterns: '**/*.nupkg' + displayName: 'Download NuGet Package' + + - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self + parameters: + packageFolderName: $(packageFolderName) + downloadPackageStep: + download: current + artifact: $(packageFolderName) + patterns: '**/*.nupkg' + displayName: 'Download NuGet Package' diff --git a/eng/pipelines/libraries/akv-variables.yml b/eng/pipelines/libraries/akv-variables.yml new file mode 100644 index 0000000000..a4aa80d555 --- /dev/null +++ b/eng/pipelines/libraries/akv-variables.yml @@ -0,0 +1,21 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - group: AKV Release Variables + - name: AKVMajor + value: 5 + - name: AKVMinor + value: 1 + - name: AKVPatch + value: 0 + + - name: AKVNugetPackageVersion + value: $(AKVMajor).$(AKVMinor).$(AKVPatch) + - name: AKVAssemblyFileVersion + value: '$(AKVMajor).$(AKVMinor)$(AKVPatch).$(Build.BuildNumber)' + - name: akvNuspecPath + value: tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec diff --git a/eng/pipelines/libraries/build-variables.yml b/eng/pipelines/libraries/build-variables.yml new file mode 100644 index 0000000000..1c26f26a69 --- /dev/null +++ b/eng/pipelines/libraries/build-variables.yml @@ -0,0 +1,10 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - template: common-variables.yml@self + - template: akv-variables.yml@self + - template: mds-variables.yml@self diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml new file mode 100644 index 0000000000..718633691b --- /dev/null +++ b/eng/pipelines/libraries/common-variables.yml @@ -0,0 +1,27 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - group: ESRP Federated Creds (AME) + # ESRPConnectedServiceName + # ESRPClientId + # AppRegistrationClientId + # AppRegistrationTenantId + # AuthAKVName + # AuthSignCertName + + - name: Configuration + value: Release + - name: CommitHead + value: '' # the value will be extracted from the repo's head + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: softwareFolder + value: $(REPOROOT)/software + - name: symbolsFolder + value: $(REPOROOT)/symbols + - name: artifactDirectory + value: '$(REPOROOT)/packages' diff --git a/eng/pipelines/libraries/mds-validation-variables.yml b/eng/pipelines/libraries/mds-validation-variables.yml new file mode 100644 index 0000000000..68bf6cad01 --- /dev/null +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -0,0 +1,34 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - template: common-variables.yml@self + - template: mds-variables.yml@self + + - name: TempFolderName # extract the nuget package here + value: temp + - name: extractedNugetPath + value: $(Build.SourcesDirectory)\$(TempFolderName)\Microsoft.Data.SqlClient.$(NugetPackageVersion) + - name: expectedFolderNames + value: lib,ref,runtimes + - name: expectedDotnetVersions + value: net462,net6.0,netstandard2.0,netstandard2.1 + - name: Database + value: Northwind + - name: platform + value: AnyCPU + - name: TargetNetFxVersion + value: net48 + - name: TargetNetCoreVersion + value: net6.0 + - name: SQLTarget + value: localhost + - name: encrypt + value: false + - name: SQL_NP_CONN_STRING + value: Data Source=np:$(SQLTarget);Initial Catalog=$(Database);Integrated Security=true;Encrypt=$(ENCRYPT);TrustServerCertificate=true; + - name: SQL_TCP_CONN_STRING + value: Data Source=tcp:$(SQLTarget);Initial Catalog=$(Database);Integrated Security=true;Encrypt=$(ENCRYPT);TrustServerCertificate=true; diff --git a/eng/pipelines/libraries/mds-variables.yml b/eng/pipelines/libraries/mds-variables.yml new file mode 100644 index 0000000000..8d5e37ab83 --- /dev/null +++ b/eng/pipelines/libraries/mds-variables.yml @@ -0,0 +1,24 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - group: Release Variables + + - name: Major + value: 5 + - name: Minor + value: 1 + - name: Patch + value: 7 + - name: Packaging.EnableSBOMSigning + value: true + + - name: NugetPackageVersion + value: $(Major).$(Minor).$(Patch) + - name: AssemblyFileVersion + value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' + - name: nuspecPath + value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' diff --git a/eng/pipelines/libraries/variables.yml b/eng/pipelines/libraries/variables.yml new file mode 100644 index 0000000000..57894459d3 --- /dev/null +++ b/eng/pipelines/libraries/variables.yml @@ -0,0 +1,17 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + - template: build-variables.yml@self + # onebranch template variables + - name: ob_outputDirectory + value: '$(artifactDirectory)' # this directory is uploaded to pipeline artifacts, reddog and cloudvault. More info at https://aka.ms/obpipelines/artifacts + - name: ob_sdl_binskim_break + value: true # https://aka.ms/obpipelines/sdl + - name: Packaging.EnableSBOMSigning + value: true + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project https://aka.ms/obpipelines/containers diff --git a/release-notes/5.1/5.1.0-preview1.md b/release-notes/5.1/5.1.0-preview1.md index 222f10f7d5..9bee9f1288 100644 --- a/release-notes/5.1/5.1.0-preview1.md +++ b/release-notes/5.1/5.1.0-preview1.md @@ -4,6 +4,11 @@ This update brings the below changes over the previous release: +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. +- [Wraith2](https://github.com/Wraith2) +- [sorensenmatias](https://github.com/sorensenmatias) + ### Fixed - Fixed `ReadAsync()` behavior to register Cancellation token action before streaming results. [#1781](https://github.com/dotnet/SqlClient/pull/1781) diff --git a/release-notes/5.1/5.1.0-preview2.md b/release-notes/5.1/5.1.0-preview2.md index 93b58078a0..5bd4a240b0 100644 --- a/release-notes/5.1/5.1.0-preview2.md +++ b/release-notes/5.1/5.1.0-preview2.md @@ -4,6 +4,12 @@ This update brings the below changes over the previous release: +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. +- [Wraith2](https://github.com/Wraith2) +- [ErikEJ](https://github.com/ErikEJ) +- [panoskj](https://github.com/panoskj) + ### Breaking changes over preview release v5.1.0-preview1 - Add support for .NET 6.0 and Dropped support for .NET Core 3.1. [#1704](https://github.com/dotnet/SqlClient/pull/1704) [#1823](https://github.com/dotnet/SqlClient/pull/1823) @@ -36,7 +42,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.0.md b/release-notes/5.1/5.1.0.md index 015b8966b9..2833f371f0 100644 --- a/release-notes/5.1/5.1.0.md +++ b/release-notes/5.1/5.1.0.md @@ -4,6 +4,15 @@ This update includes the following changes over the 5.0 release: +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. +- [Wraith2](https://github.com/Wraith2) +- [ErikEJ](https://github.com/ErikEJ) +- [roji](https://github.com/roji) +- [panoskj](https://github.com/panoskj) +- [teo-tsirpanis](https://github.com/teo-tsirpanis) +- [sorensenmatias](https://github.com/sorensenmatias) + ### Breaking changes - Dropped support for .NET Core 3.1. [#1704](https://github.com/dotnet/SqlClient/pull/1704) [#1823](https://github.com/dotnet/SqlClient/pull/1823) @@ -50,7 +59,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) @@ -70,7 +79,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri #### .NET -- Microsoft.Data.SqlClient.SNI 5.1.0 +- Microsoft.Data.SqlClient.SNI.runtime 5.1.0 - Azure.Identity 1.7.0 - Microsoft.Identity.Client 4.47.2 - Microsoft.IdentityModel.JsonWebTokens 6.24.0 @@ -86,7 +95,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri #### .NET Standard -- Microsoft.Data.SqlClient.SNI 5.1.0 +- Microsoft.Data.SqlClient.SNI.runtime 5.1.0 - Azure.Identity 1.7.0 - Microsoft.Identity.Client 4.47.2 - Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 diff --git a/release-notes/5.1/5.1.1.md b/release-notes/5.1/5.1.1.md new file mode 100644 index 0000000000..fea3f68033 --- /dev/null +++ b/release-notes/5.1/5.1.1.md @@ -0,0 +1,70 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.1 released 28 March 2023 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. + +### Fixed + +- Fixed an incorrect exception when a symmetric key fails to decrypt a column using Always Encrypted. [#1968](https://github.com/dotnet/SqlClient/pull/1968) +- Fixed `TransactionScope` connection issue when `Enlist` is `enabled`, `Pooling` is `disabled`, and `Network Connection Type` is set to `Redirect`. [#1967](https://github.com/dotnet/SqlClient/pull/1967) +- Fixed throttling of token requests by calling `AcquireTokenSilent`. [#1966](https://github.com/dotnet/SqlClient/pull/1966) +- Fixed TDS RPC error on large queries in `SqlCommand.ExecuteReaderAsync`. [#1965](https://github.com/dotnet/SqlClient/pull/1965) +- Fixed `NullReferenceException` in `GetBytesAsync`. [#1964](https://github.com/dotnet/SqlClient/pull/1964) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.0 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.0 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.0 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.2.md b/release-notes/5.1/5.1.2.md new file mode 100644 index 0000000000..20a71f34ab --- /dev/null +++ b/release-notes/5.1/5.1.2.md @@ -0,0 +1,78 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.2 released 26 October 2023 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. +- [emidah](https://github.com/emidah) + +### Fixed + +- Fixed access violation when using SQL Express user instance. [#2101](https://github.com/dotnet/SqlClient/pull/2101) +- Fixed Always Encrypted secure enclave retry logic for async queries. [#1988](https://github.com/dotnet/SqlClient/pull/1988) +- Fixed LocalDb and managed SNI by improving the error messages and avoid falling back to the local service. [#2129](https://github.com/dotnet/SqlClient/pull/2129) +- Fixed .NET and .NET Standard file version. [2093](https://github.com/dotnet/SqlClient/pull/2093) +- Fixed non-string values and `SqlConnectionStringBuilder` property indexer issue. [#2018](https://github.com/dotnet/SqlClient/pull/2018) +- Fixed `SqlConnectionEncryptOption` type conversion by introducing the `SqlConnectionEncryptOptionConverter` attribute when using **appsettings.json** files. [#2057](https://github.com/dotnet/SqlClient/pull/2057) +- Fixed Transient fault handling issue with `OpenAsync`. [#1983](https://github.com/dotnet/SqlClient/pull/1983) +- Fixed activity correlator to continue use of same GUID for connection activity. [#1997](https://github.com/dotnet/SqlClient/pull/1997) + +### Changed + +- Updated `Microsoft.Data.SqlClient.SNI` (.NET Framework dependency) and `Microsoft.Data.SqlClient.SNI.runtime` (.NET Core/Standard dependency) version to `5.1.1`. [#2123](https://github.com/dotnet/SqlClient/pull/2123) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.3.md b/release-notes/5.1/5.1.3.md new file mode 100644 index 0000000000..f96e347212 --- /dev/null +++ b/release-notes/5.1/5.1.3.md @@ -0,0 +1,67 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.3 released 9 January 2024 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. + +### Fixed + +- Fixed encryption downgrade issue. [CVE-2024-0056](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-0056) +- Fixed certificate chain validation logic flow. + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.7.0 +- Microsoft.Identity.Client 4.47.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.4.md b/release-notes/5.1/5.1.4.md new file mode 100644 index 0000000000..62c325750e --- /dev/null +++ b/release-notes/5.1/5.1.4.md @@ -0,0 +1,70 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.4 released 9 January 2024 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. + +### Fixed + +- Fixed a deadlock problem for distributed transactions when on .NET. + +### Changed + +- Upgraded `Azure.Identity` dependency version to [1.10.3](https://www.nuget.org/packages/Azure.Identity/1.10.3) to address [CVE-2023-36414](https://github.com/advisories/GHSA-5mfx-4wcx-rv27). + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.24.0 +- Microsoft.IdentityModel.JsonWebTokens 6.24.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.5.md b/release-notes/5.1/5.1.5.md new file mode 100644 index 0000000000..540061f8f1 --- /dev/null +++ b/release-notes/5.1/5.1.5.md @@ -0,0 +1,72 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.5 released 29 January 2024 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. +- [ErikEJ](https://github.com/ErikEJ) + +### Fixed + +- Fixed connection to unsubscribe from transaction completion events before returning it to the connection pool [#2321](https://github.com/dotnet/SqlClient/pull/2321) +- Fixed InvalidCastException when reading an Always Encrypted date or time column [#2324](https://github.com/dotnet/SqlClient/pull/2324) + +### Changed + +- Changed Microsoft.IdentityModel.JsonWebTokens and Microsoft.IdentityModel.Protocols.OpenIdConnect version 6.24.0 to 6.35.0 [#2320](https://github.com/dotnet/SqlClient/pull/2320) to address [CVE-2024-21319](https://www.cve.org/CVERecord?id=CVE-2024-21319) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.10.3 +- Microsoft.Identity.Client 4.56.2 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.6.md b/release-notes/5.1/5.1.6.md new file mode 100644 index 0000000000..3c6cb9ebe6 --- /dev/null +++ b/release-notes/5.1/5.1.6.md @@ -0,0 +1,75 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.6 released 27 August 2024 + +This update includes the following changes over the previous release: + +### Contributors +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. + +### Fixed + +- Fixed Transient fault handling issue with `OpenAsync`. [#1983](https://github.com/dotnet/SqlClient/pull/1983) [#2508](https://github.com/dotnet/SqlClient/pull/2508) +- Fixed `AcquireTokenAsync` timeout handling for edge cases in `ActiveDirectoryAuthenticationProvider`. [#2706](https://github.com/dotnet/SqlClient/pull/2706) +- Fixed pending data with `SqlDataReader` against an encrypted column. [#2618](https://github.com/dotnet/SqlClient/pull/2618) [#2818](https://github.com/dotnet/SqlClient/pull/2818) + +### Changed + +- Upgraded `Azure.Identity` version from 1.11.3 to 1.11.4 [#2649] (https://github.com/dotnet/SqlClient/pull/2649) [#2529] (https://github.com/dotnet/SqlClient/pull/2529) to address [CVE-2024-35255](https://github.com/advisories/GHSA-m5vv-6r4h-3vj9). +- Upgraded `Microsoft.Identity.Client` version from 4.60.0 to 4.61.3 [#2649] (https://github.com/dotnet/SqlClient/pull/2649) [#2529] (https://github.com/dotnet/SqlClient/pull/2529) to address [CVE-2024-35255](https://github.com/advisories/GHSA-m5vv-6r4h-3vj9). +- Added caching to `TokenCredential` objects to take advantage of token caching. [#2776](https://github.com/dotnet/SqlClient/pull/2776) +- Code health improvements: [#2490] (https://github.com/dotnet/SqlClient/pull/2490) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.1.1 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encodings.Web 6.0.0 + +#### .NET + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.0 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 5.1.1 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.7.md b/release-notes/5.1/5.1.7.md new file mode 100644 index 0000000000..f8716c7b21 --- /dev/null +++ b/release-notes/5.1/5.1.7.md @@ -0,0 +1,76 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.7 - April 25, 2025 + +This update brings the following changes since the 5.1.6 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3285](https://github.com/dotnet/SqlClient/pull/3285)) +- Fixed inconsistencies between source and reference projects (PR [#3180](https://github.com/dotnet/SqlClient/pull/3180)) + +### Changed + +- Updated the following dependencies: + - [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/5.1.2) 5.1.1 to 5.1.2 for .NET Framework on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/5.1.2) 5.1.1 to 5.1.2 for .NET on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3068](https://github.com/dotnet/SqlClient/pull/3068)) + - [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive dependency on vulnerable [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.0) 6.0.0 (PR [#3207](https://github.com/dotnet/SqlClient/pull/3207)) + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3077](https://github.com/dotnet/SqlClient/pull/3077)) + - [System.Text.Encodings.Web](https://www.nuget.org/packages/System.Text.Encodings.Web/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive downgrade for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + - [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.11) 6.0.11 - Avoid transitive dependencies on older vulnerable versions for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +## Dependencies + +### .NET Framework + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.1 +- System.Text.Json 6.0.11 + +### .NET + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.1 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +### .NET Standard + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.1 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.md b/release-notes/5.1/5.1.md deleted file mode 100644 index 6f6687b5d5..0000000000 --- a/release-notes/5.1/5.1.md +++ /dev/null @@ -1,14 +0,0 @@ -# Microsoft.Data.SqlClient 5.1 Releases - -The following Microsoft.Data.SqlClient 5.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2023/01/19 | 5.1.0 | [release notes](5.1.0.md) | - -The following Microsoft.Data.SqlClient 5.1 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2022/11/10 | 5.1.0-preview2.22314.2 | [release notes](5.1.0-preview2.md) | -| 2022/10/19 | 5.1.0-preview1.22279.3 | [release notes](5.1.0-preview1.md) | diff --git a/release-notes/5.1/README.md b/release-notes/5.1/README.md index 6f6687b5d5..27ed889b2a 100644 --- a/release-notes/5.1/README.md +++ b/release-notes/5.1/README.md @@ -4,6 +4,13 @@ The following Microsoft.Data.SqlClient 5.1 stable releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2025/04/25 | 5.1.7 | [release notes](5.1.7.md) | +| 2024/08/27 | 5.1.6 | [release notes](5.1.6.md) | +| 2024/01/29 | 5.1.5 | [release notes](5.1.5.md) | +| 2024/01/09 | 5.1.4 | [release notes](5.1.4.md) | +| 2024/01/09 | 5.1.3 | [release notes](5.1.3.md) | +| 2023/10/26 | 5.1.2 | [release notes](5.1.2.md) | +| 2023/03/28 | 5.1.1 | [release notes](5.1.1.md) | | 2023/01/19 | 5.1.0 | [release notes](5.1.0.md) | The following Microsoft.Data.SqlClient 5.1 preview releases have been shipped: diff --git a/release-notes/README.md b/release-notes/README.md index ba76fe8433..2fbfc1de70 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -1,6 +1,6 @@ # Microsoft.Data.SqlClient Release Notes -The latest stable release is [Microsoft.Data.SqlClient 5.0](5.0). +The latest stable release is [Microsoft.Data.SqlClient 5.1](5.1). ## Release Information @@ -8,8 +8,8 @@ The latest stable release is [Microsoft.Data.SqlClient 5.0](5.0). - [Microsoft.Data.SqlClient 5.0](5.0) - [Microsoft.Data.SqlClient 4.1](4.1) - [Microsoft.Data.SqlClient 4.0](4.0) -- [Microsoft.Data.SqlClient 3.0](3.0) - [Microsoft.Data.SqlClient 3.1](3.1) +- [Microsoft.Data.SqlClient 3.0](3.0) - [Microsoft.Data.SqlClient 2.1](2.1) - [Microsoft.Data.SqlClient 2.0](2.0) - [Microsoft.Data.SqlClient 1.1](1.1) @@ -17,10 +17,11 @@ The latest stable release is [Microsoft.Data.SqlClient 5.0](5.0). # Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider Release Notes -The latest stable release is [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 3.0](add-ons/AzureKeyVaultProvider/3.0). +The latest stable release is [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 5.1](add-ons/AzureKeyVaultProvider/5.1). ## Release Information +- [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 5.1](add-ons/AzureKeyVaultProvider/5.1) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 3.0](add-ons/AzureKeyVaultProvider/3.0) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 2.0](add-ons/AzureKeyVaultProvider/2.0) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 1.2](add-ons/AzureKeyVaultProvider/1.2) diff --git a/release-notes/add-ons/AzureKeyVaultProvider/5.1/5.1.0.md b/release-notes/add-ons/AzureKeyVaultProvider/5.1/5.1.0.md new file mode 100644 index 0000000000..159a1072d5 --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/5.1/5.1.0.md @@ -0,0 +1,72 @@ +# Release Notes + +## General Availability of Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + +_**5.1.0 released 01 February 2024**_ + +This library contains the implementation of `Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider` for accessing Azure Key Vault, and the provider class is named `SqlColumnEncryptionAzureKeyVaultProvider`. + +### Changed + +- Changed Microsoft.Data.SqlClient version 3.0.0 to 5.1.5 [#2330](https://github.com/dotnet/SqlClient/pull/2330) +- Changed Azure.Core version 1.6.0 to 1.35.0 [#2330](https://github.com/dotnet/SqlClient/pull/2330) +- Changed Azure.Security.KeyVault.Keys 4.0.3 to 4.5.0 [#2330](https://github.com/dotnet/SqlClient/pull/2330) +- Changed Microsoft.Extensions.Caching.Memory 5.0.0 to 8.0.0 for .Net 8.0 and 6.0.1 for other Target frameworks [#2330](https://github.com/dotnet/SqlClient/pull/2330) + +### Working with SQLColumnEncryptionAzureKeyVaultProvider + +`SqlColumnEncryptionAzureKeyVaultProvider` **v5.1** is implemented against `Microsoft.Data.SqlClient` **v5.1** and supports .NET Framework 4.6.2+, .NET Core 6.0+, and .NET Standard 2.0+. The provider name identifier for this library is "**AZURE_KEY_VAULT**" and it is not registered in the driver by default. Client applications may initialize this provider by providing an `Azure.Core.TokenCredential` and registering it with the driver using any of the below APIs: + +- [SqlConnection.RegisterColumnEncryptionKeyStoreProviders](https://docs.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlconnection.registercolumnencryptionkeystoreproviders?view=sqlclient-dotnet-5.1) +- [SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection](https://docs.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlconnection.registercolumnencryptionkeystoreprovidersonconnection?view=sqlclient-dotnet-5.1) (Added in version 3.0.0) +- [SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand](https://docs.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlcommand.registercolumnencryptionkeystoreprovidersoncommand?view=sqlclient-dotnet-5.1) (Added in version 3.0.0) + +Once the provider is registered, it can be used to perform Always Encrypted operations by creating a Column Master Key using the Azure Key Vault Key Identifier URL. + +The linked C# samples below demonstrate using Always Encrypted with secure enclaves with Azure Key Vault: + +- Legacy API support (Always Encrypted): [AzureKeyVaultProviderExample.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/AzureKeyVaultProviderLegacyExample_2_0.cs) +- New API support (Always Encrypted): [AzureKeyVaultProviderExample.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/AzureKeyVaultProviderExample_2_0.cs) +- Legacy API support (Always Encrypted with secure enclaves): [AzureKeyVaultProviderExample.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/doc\samples\AzureKeyVaultProviderWithEnclaveProviderExample.cs) +- New API support (Always Encrypted with secure enclaves): [AzureKeyVaultProviderExample.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/doc\samples\AzureKeyVaultProviderWithEnclaveProviderExample_2_0.cs) +- Column Encryption Key cache scope example: [AzureKeyVaultProvider_ColumnEncryptionKeyCacheScope.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/AzureKeyVaultProvider_ColumnEncryptionKeyCacheScope.cs) +- Registering custom key store provider - Connection Precedence: [RegisterCustomKeyStoreProvider_ConnectionPrecedence.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/RegisterCustomKeyStoreProvider_ConnectionPrecedence.cs) +- Registering custom key store provider - Command Precedence: [RegisterCustomKeyStoreProvider_CommandPrecedence.cs](https://github.com/dotnet/SqlClient/blob/main/doc/samples/RegisterCustomKeyStoreProvider_CommandPrecedence.cs) + +For further details, refer to [Using the Azure Key Vault provider](https://docs.microsoft.com/sql/connect/ado-net/sql/sqlclient-support-always-encrypted#using-the-azure-key-vault-provider) + +## Target Platform Support + +- .NET Framework 4.6.2+ +- .NET Core 6.0+ (Windows x86, Windows x64, Linux, macOS) +- .NET Standard 2.0+ + +### Dependencies + +#### .NET Framework + +- Azure.Core 1.35.0 +- Azure.Security.KeyVault.Keys 4.5.0 +- Microsoft.Data.SqlClient 5.1.5 +- Microsoft.Extensions.Caching.Memory 6.0.1 + +##### .NET 6 + +- Azure.Core 1.35.0 +- Azure.Security.KeyVault.Keys 4.5.0 +- Microsoft.Data.SqlClient 5.1.5 +- Microsoft.Extensions.Caching.Memory 6.0.1 + +#### .NET 8 + +- Azure.Core 1.35.0 +- Azure.Security.KeyVault.Keys 4.5.0 +- Microsoft.Data.SqlClient 5.1.5 +- Microsoft.Extensions.Caching.Memory 8.0.0 + +#### .NET Standard + +- Azure.Core 1.35.0 +- Azure.Security.KeyVault.Keys 4.5.0 +- Microsoft.Data.SqlClient 5.1.5 +- Microsoft.Extensions.Caching.Memory 6.0.1 diff --git a/release-notes/add-ons/AzureKeyVaultProvider/5.1/README.md b/release-notes/add-ons/AzureKeyVaultProvider/5.1/README.md new file mode 100644 index 0000000000..55d9d38bc4 --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/5.1/README.md @@ -0,0 +1,7 @@ +# Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 5.1 Releases + +The following Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 5.1 stable releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2024/02/01 | 5.1.0 | [release notes](5.1.0.md) | diff --git a/src/Directory.Build.props b/src/Directory.Build.props index afcf503118..be2fe1f22a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -61,11 +61,40 @@ $(NuGetRoot)nuget.exe true + $(WarningsNotAsErrors);NU1901;NU1902;NU1903;NU1904;NU1905 false $(DefineConstants);ENCLAVE_SIMULATOR + + + + + false + + + all + + + + + portable + true + false + true + true + false + + + true + @@ -74,4 +103,6 @@ + + diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj index 3f2267451e..ca570955ca 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj @@ -15,6 +15,14 @@ false MIT true + + + + true + $(SigningKeyPath) + + + $(SigningKeyPath) @@ -22,7 +30,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 41e1263abc..912d68f5de 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -3,6 +3,9 @@ false net6.0;netstandard2.0;netstandard2.1 netstandard2.1 + $(OS) + true + true $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ $(OutputPath)\$(TargetFramework)\Microsoft.Data.SqlClient.xml @@ -12,17 +15,53 @@ netstandard AnyCPU;x64;x86 + + true + $(SigningKeyPath) + $(SigningKeyPath) + + + $(SigningKeyPath) + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..f06bc9df7b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -19,6 +19,14 @@ true Core $(BaseProduct) + + true + $(SigningKeyPath) + $(SigningKeyPath) + + + $(SigningKeyPath) + portable true @@ -328,6 +336,9 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs @@ -956,7 +967,7 @@ - + @@ -969,7 +980,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index 8fc8df24e0..9f4d5d2e08 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -16,6 +16,7 @@ internal abstract partial class DbConnectionInternal { private static int _objectTypeCount; internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); + private TransactionCompletedEventHandler _transactionCompletedEventHandler = null; private bool _isInStasis; @@ -437,15 +438,19 @@ internal void DetachTransaction(Transaction transaction, bool isExplicitlyReleas // potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new // transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should // be the exception, not the rule. - lock (this) + // locking on anything other than the transaction object would lead to a thread deadlock with sys.Transaction.TransactionCompleted event. + lock (transaction) { // Detach if detach-on-end behavior, or if outer connection was closed - DbConnection owner = (DbConnection)Owner; - if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner) + DbConnection owner = Owner; + if (isExplicitlyReleasing || UnbindOnTransactionCompletion || owner is null) { Transaction currentEnlistedTransaction = _enlistedTransaction; if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction)) { + // We need to remove the transaction completed event handler to cease listening for the transaction to end. + currentEnlistedTransaction.TransactionCompleted -= _transactionCompletedEventHandler; + EnlistedTransaction = null; if (IsTxRootWaitingForTxEnd) @@ -479,7 +484,8 @@ void TransactionCompletedEvent(object sender, TransactionEventArgs e) private void TransactionOutcomeEnlist(Transaction transaction) { - transaction.TransactionCompleted += new TransactionCompletedEventHandler(TransactionCompletedEvent); + _transactionCompletedEventHandler ??= new TransactionCompletedEventHandler(TransactionCompletedEvent); + transaction.TransactionCompleted += _transactionCompletedEventHandler; } internal void SetInStasis() diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs index 68eaa25486..a81fb89dfb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs @@ -50,8 +50,14 @@ private string GetConnectionString(string localDbInstance) { StringBuilder localDBConnectionString = new StringBuilder(MAX_LOCAL_DB_CONNECTION_STRING_SIZE + 1); int sizeOfbuffer = localDBConnectionString.Capacity; - localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer); - return localDBConnectionString.ToString(); + int result = localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer); + if (result != TdsEnums.SNI_SUCCESS) + { + SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBErrorCode, Strings.SNI_ERROR_50); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Unsuccessful 'LocalDBStartInstance' method call with {0} result to start '{1}' localDb instance", args0: result, args1: localDbInstance); + localDBConnectionString = null; + } + return localDBConnectionString?.ToString(); } internal enum LocalDBErrorState diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs index b92746098f..08272596d8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs @@ -21,16 +21,18 @@ namespace Microsoft.Data.SqlClient.SNI /// internal enum SNIProviders { - HTTP_PROV, // HTTP Provider - NP_PROV, // Named Pipes Provider - SESSION_PROV, // Session Provider - SIGN_PROV, // Sign Provider - SM_PROV, // Shared Memory Provider - SMUX_PROV, // SMUX Provider - SSL_PROV, // SSL Provider - TCP_PROV, // TCP Provider - MAX_PROVS, // Number of providers - INVALID_PROV // SQL Network Interfaces + HTTP_PROV = 0, // HTTP Provider + NP_PROV = 1, // Named Pipes Provider + SESSION_PROV = 2, // Session Provider + SIGN_PROV = 3, // Sign Provider + SM_PROV = 4, // Shared Memory Provider + SMUX_PROV = 5, // SMUX Provider + SSL_PROV = 6, // SSL Provider + TCP_PROV = 7, // TCP Provider + VIA_PROV = 8, // Virtual Interface Architecture Provider + CTAIP_PROV = 9, + MAX_PROVS = 10, // Number of providers + INVALID_PROV = 11 // SQL Network Interfaces } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index ac4d3599dd..834050b4bb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -190,7 +190,7 @@ internal static SNIHandle CreateConnectionHandle( case DataSource.Protocol.TCP: sniHandle = CreateTcpHandle(details, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst, hostNameInCertificate, serverCertificateFilename); - break; + break; case DataSource.Protocol.NP: sniHandle = CreateNpHandle(details, timerExpire, parallel, tlsFirst); break; @@ -392,7 +392,7 @@ private static string GetLocalDBDataSource(string fullServerName, out bool error Debug.Assert(!string.IsNullOrWhiteSpace(localDBInstance), "Local DB Instance name cannot be empty."); localDBConnectionString = LocalDB.GetLocalDBConnectionString(localDBInstance); - if (fullServerName == null) + if (fullServerName == null || string.IsNullOrEmpty(localDBConnectionString)) { // The Last error is set in LocalDB.GetLocalDBConnectionString. We don't need to set Last here. error = true; @@ -522,7 +522,18 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) ReadOnlySpan input = dataSource.AsSpan().TrimStart(); error = false; // NetStandard 2.0 does not support passing a string to ReadOnlySpan - if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) + int index = input.IndexOf(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase); + if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) + { + instanceName = input.Trim().ToString(); + } + else if (index > 0) + { + SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.ErrorLocatingServerInstance, Strings.SNI_ERROR_26); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIProxy), EventType.ERR, "Incompatible use of prefix with LocalDb: '{0}'", dataSource); + error = true; + } + else if (index == 0) { // When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index // Such ad input = input[1..]; @@ -541,10 +552,6 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) error = true; } } - else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) - { - instanceName = input.Trim().ToString(); - } return instanceName; } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs index 103af2ed2d..b8e38978b9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs @@ -765,17 +765,29 @@ public override uint Send(SNIPacket packet) } /// - /// Receive a packet synchronously + /// Receives a packet synchronously. /// - /// SNI packet - /// Timeout in Milliseconds - /// SNI error code + /// The received SNI packet. + /// + /// Timeout in milliseconds: + /// - If greater than 0, sets the socket's receive timeout to the specified value. + /// - If equal to -1, represents an infinite timeout (socket timeout is set to 0). + /// - If less than -1 or equal to 0, results in a timeout error. + /// + /// SNI error code indicating the result of the operation. public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) { SNIPacket errorPacket; lock (this) { packet = null; + + if (_socket == null) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Socket is null.", args0: _connectionId); + return ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, Strings.SNI_ERROR_10); + } + try { if (timeoutInMilliseconds > 0) @@ -784,8 +796,7 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) } else if (timeoutInMilliseconds == -1) { - // SqlClient internally represents infinite timeout by -1, and for TcpClient this is translated to a timeout of 0 - _socket.ReceiveTimeout = 0; + _socket.ReceiveTimeout = Timeout.Infinite; } else { @@ -840,7 +851,9 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) } finally { - _socket.ReceiveTimeout = 0; + // Reset the socket timeout to Timeout.Infinite after the receive operation is done + // to avoid blocking the thread in case of a timeout error. + _socket.ReceiveTimeout = Timeout.Infinite; } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs index e51175059a..49d68b0120 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs @@ -48,8 +48,14 @@ internal static int GetPortByInstanceName(string browserHostName, string instanc } catch (SocketException se) { + // A SocketException is possible for an instance name that doesn't exist. + // If there are multiple IP addresses and one of them fails with a SocketException but + // others simply don't respond because the instance name is invalid, we want to return + // the same error as if the response was empty. The higher error suits all scenarios. + // But log it, just in case there is a different, underlying issue that support needs + // to troubleshoot. SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.ERR, "SocketException Message = {0}", args0: se?.Message); - throw new Exception(SQLMessage.SqlServerBrowserNotAccessible(), se); + throw; } const byte SvrResp = 0x05; @@ -321,9 +327,37 @@ private static SsrpResult SendUDPRequest(IPEndPoint endPoint, byte[] requestPack } } } + catch (AggregateException ae) + { + if (ae.InnerExceptions.Count > 0) + { + // Log all errors + foreach (Exception e in ae.InnerExceptions) + { + // Favor SocketException for returned error + if (e is SocketException) + { + result.Error = e; + } + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: e.Message); + } + + // Return first error if we didn't find a SocketException + result.Error = result.Error == null ? ae.InnerExceptions[0] : result.Error; + } + else + { + result.Error = ae; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: ae.Message); + } + } catch (Exception e) { result.Error = e; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: e.Message); } return result; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs index cca04fc323..fd81db557d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs @@ -15,8 +15,8 @@ internal abstract partial class SqlColumnEncryptionEnclaveProvider /// The information the provider uses to attest the enclave and generate a symmetric key for the session. The format of this information is specific to the enclave attestation protocol. /// A Diffie-Hellman algorithm object encapsulating a client-side key pair. /// The set of parameters required for enclave session. - /// The set of extra data needed for attestating the enclave. - /// The length of the extra data needed for attestating the enclave. + /// The set of extra data needed for attesting the enclave. + /// The length of the extra data needed for attesting the enclave. /// The requested enclave session or null if the provider does not implement session caching. /// A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellman clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index e374d43665..9223702509 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -8,7 +8,7 @@ namespace Microsoft.Data.SqlClient internal abstract partial class SqlColumnEncryptionEnclaveProvider { /// - internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); + internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index b576ef3510..f3b26e7899 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -137,6 +137,11 @@ protected override void AfterCleared(SqlCommand owner) /// Internal flag for testing purposes that forces all queries to internally end async calls. /// private static bool _forceInternalEndQuery = false; + + /// + /// Internal flag for testing purposes that forces one RetryableEnclaveQueryExecutionException during GenerateEnclavePackage + /// + private static bool _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; #endif private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); @@ -2221,7 +2226,7 @@ private IAsyncResult BeginExecuteReaderInternal(CommandBehavior behavior, AsyncC // back into pool when we should not. } - bool usedCache; + bool usedCache = false; Task writeTask = null; try { @@ -2238,7 +2243,10 @@ private IAsyncResult BeginExecuteReaderInternal(CommandBehavior behavior, AsyncC // For async, RunExecuteReader will never put the stateObj back into the pool, so do so now. ReliablePutStateObject(); - throw; + if (inRetry || e is not EnclaveDelegate.RetryableEnclaveQueryExecutionException) + { + throw; + } } if (writeTask != null) @@ -2439,12 +2447,7 @@ long firstAttemptStart // Remove the entry from the cache since it was inconsistent. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, - enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); try { @@ -2480,6 +2483,26 @@ long firstAttemptStart }, TaskScheduler.Default); } + private void InvalidateEnclaveSession() + { + if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) + { + EnclaveDelegate.Instance.InvalidateEnclaveSession( + this._activeConnection.AttestationProtocol, + this._activeConnection.Parser.EnclaveType, + GetEnclaveSessionParameters(), + this.enclavePackage.EnclaveSession); + } + } + + private EnclaveSessionParameters GetEnclaveSessionParameters() + { + return new EnclaveSessionParameters( + this._activeConnection.DataSource, + this._activeConnection.EnclaveAttestationUrl, + this._activeConnection.Database); + } + private void BeginExecuteReaderInternalReadStage(TaskCompletionSource completion) { Debug.Assert(completion != null, "CompletionSource should not be null"); @@ -3660,7 +3683,13 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r try { // Fetch the encryption information that applies to any of the input parameters. - describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout, isAsync, asyncWrite, out describeParameterEncryptionNeeded, out fetchInputParameterEncryptionInfoTask, out describeParameterEncryptionRpcOriginalRpcMap); + describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout, + isAsync, + asyncWrite, + out describeParameterEncryptionNeeded, + out fetchInputParameterEncryptionInfoTask, + out describeParameterEncryptionRpcOriginalRpcMap, + inRetry); Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null, "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request."); @@ -3695,7 +3724,13 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r // Mark that we should not process the finally block since we have async execution pending. // Note that this should be done outside the task's continuation delegate. processFinallyBlock = false; - describeParameterEncryptionDataReader = GetParameterEncryptionDataReader(out returnTask, fetchInputParameterEncryptionInfoTask, describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, describeParameterEncryptionNeeded); + describeParameterEncryptionDataReader = GetParameterEncryptionDataReader( + out returnTask, + fetchInputParameterEncryptionInfoTask, + describeParameterEncryptionDataReader, + describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionNeeded, + inRetry); decrementAsyncCountInFinallyBlock = false; } @@ -3707,14 +3742,22 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r // Mark that we should not process the finally block since we have async execution pending. // Note that this should be done outside the task's continuation delegate. processFinallyBlock = false; - describeParameterEncryptionDataReader = GetParameterEncryptionDataReaderAsync(out returnTask, describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, describeParameterEncryptionNeeded); + describeParameterEncryptionDataReader = GetParameterEncryptionDataReaderAsync( + out returnTask, + describeParameterEncryptionDataReader, + describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionNeeded, + inRetry); decrementAsyncCountInFinallyBlock = false; } else { // For synchronous execution, read the results of describe parameter encryption here. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + ReadDescribeEncryptionParameterResults( + describeParameterEncryptionDataReader, + describeParameterEncryptionRpcOriginalRpcMap, + inRetry); } #if DEBUG @@ -3761,7 +3804,7 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r private SqlDataReader GetParameterEncryptionDataReader(out Task returnTask, Task fetchInputParameterEncryptionInfoTask, SqlDataReader describeParameterEncryptionDataReader, - ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool describeParameterEncryptionNeeded) + ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool describeParameterEncryptionNeeded, bool inRetry) { returnTask = AsyncHelper.CreateContinuationTaskWithState(fetchInputParameterEncryptionInfoTask, this, (object state) => @@ -3791,7 +3834,7 @@ private SqlDataReader GetParameterEncryptionDataReader(out Task returnTask, Task Debug.Assert(null == command._stateObj, "non-null state object in PrepareForTransparentEncryption."); // Read the results of describe parameter encryption. - command.ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + command.ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); #if DEBUG // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. @@ -3836,7 +3879,7 @@ private SqlDataReader GetParameterEncryptionDataReader(out Task returnTask, Task private SqlDataReader GetParameterEncryptionDataReaderAsync(out Task returnTask, SqlDataReader describeParameterEncryptionDataReader, - ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool describeParameterEncryptionNeeded) + ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool describeParameterEncryptionNeeded, bool inRetry) { returnTask = Task.Run(() => { @@ -3866,7 +3909,7 @@ private SqlDataReader GetParameterEncryptionDataReaderAsync(out Task returnTask, // Read the results of describe parameter encryption. ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, - describeParameterEncryptionRpcOriginalRpcMap); + describeParameterEncryptionRpcOriginalRpcMap, inRetry); #if DEBUG // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. if (_sleepAfterReadDescribeEncryptionParameterResults) @@ -3903,13 +3946,15 @@ private SqlDataReader GetParameterEncryptionDataReaderAsync(out Task returnTask, /// /// /// + /// Indicates if this is a retry from a failed call. /// private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, bool isAsync, bool asyncWrite, out bool inputParameterEncryptionNeeded, out Task task, - out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) + out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, + bool isRetry) { inputParameterEncryptionNeeded = false; task = null; @@ -3921,10 +3966,10 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, SqlConnectionAttestationProtocol attestationProtocol = this._activeConnection.AttestationProtocol; string enclaveType = this._activeConnection.Parser.EnclaveType; - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); + EnclaveSessionParameters enclaveSessionParameters = GetEnclaveSessionParameters(); SqlEnclaveSession sqlEnclaveSession = null; - EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, true, out sqlEnclaveSession, out customData, out customDataLength); + EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, true, isRetry, out sqlEnclaveSession, out customData, out customDataLength); if (sqlEnclaveSession == null) { enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType, enclaveSessionParameters.AttestationUrl, customData, customDataLength); @@ -4170,7 +4215,8 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques /// /// Resultset from calling to sp_describe_parameter_encryption /// Readonly dictionary with the map of parameter encryption rpc requests with the corresponding original rpc requests. - private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) + /// Indicates if this is a retry from a failed call. + private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool isRetry) { _SqlRPC rpc = null; int currentOrdinal = -1; @@ -4449,9 +4495,16 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi SqlConnectionAttestationProtocol attestationProtocol = this._activeConnection.AttestationProtocol; string enclaveType = this._activeConnection.Parser.EnclaveType; - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, attestationInfo, enclaveAttestationParameters, customData, customDataLength); + EnclaveDelegate.Instance.CreateEnclaveSession( + attestationProtocol, + enclaveType, + GetEnclaveSessionParameters(), + attestationInfo, + enclaveAttestationParameters, + customData, + customDataLength, + isRetry); enclaveAttestationParameters = null; attestationInfoRead = true; } @@ -4552,7 +4605,7 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) { - if (inRetry || isAsync) + if (inRetry) { throw; } @@ -4561,22 +4614,16 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior // First invalidate the entry from the cache, so that we refresh our encryption MD. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, - enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); - return RunExecuteReader(cmdBehavior, runBehavior, returnStream, null, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, isAsync, inRetry: true, method: method); + return RunExecuteReader(cmdBehavior, runBehavior, returnStream, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, isAsync, inRetry: true, method: method); } catch (SqlException ex) { // We only want to retry once, so don't retry if we are already in retry. // If we didn't use the cache, we don't want to retry. - // The async retried are handled separately, handle only sync calls here. - if (inRetry || isAsync || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) + if (inRetry || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) { throw; } @@ -4605,14 +4652,9 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior // First invalidate the entry from the cache, so that we refresh our encryption MD. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, - enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); - return RunExecuteReader(cmdBehavior, runBehavior, returnStream, null, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, isAsync, inRetry: true, method: method); + return RunExecuteReader(cmdBehavior, runBehavior, returnStream, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, isAsync, inRetry: true, method: method); } } } @@ -4712,9 +4754,15 @@ private void GenerateEnclavePackage() try { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); +#if DEBUG + if (_forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage) + { + _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; + throw new EnclaveDelegate.RetryableEnclaveQueryExecutionException("testing", null); + } +#endif this.enclavePackage = EnclaveDelegate.Instance.GenerateEnclavePackage(attestationProtocol, keysToBeSentToEnclave, - this.CommandText, enclaveType, enclaveSessionParameters, _activeConnection, this); + this.CommandText, enclaveType, GetEnclaveSessionParameters(), _activeConnection, this); } catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) { @@ -4772,8 +4820,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi bool processFinallyBlock = true; bool decrementAsyncCountOnFailure = false; - // If we are in retry, don't increment the Async count. This should have already been set. - if (isAsync && !inRetry) + if (isAsync) { _activeConnection.GetOpenTdsConnection().IncrementAsyncCount(); decrementAsyncCountOnFailure = true; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 8d43d72685..d72fbbf4c9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -2844,11 +2844,11 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met return (T)(object)data.DateTime; } #if NET6_0_OR_GREATER - else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) { return (T)(object)data.DateOnly; } - else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) { return (T)(object)data.TimeOnly; } @@ -5285,7 +5285,7 @@ protected override void DisposeCore() { TDisposable copy = _disposable; _disposable = default; - copy.Dispose(); + copy?.Dispose(); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index 680b34079d..3ed756eef0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -159,17 +159,17 @@ public byte[] Promote() ValidateActiveOnConnection(connection); connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); - returnValue = _connection.PromotedDTCToken; + returnValue = connection.PromotedDTCToken; // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. - if (_connection.IsGlobalTransaction) + if (connection.IsGlobalTransaction) { if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) { throw SQL.UnsupportedSysTxForGlobalTransactions(); } - if (!_connection.IsGlobalTransactionsEnabledForServer) + if (!connection.IsGlobalTransactionsEnabledForServer) { throw SQL.GlobalTransactionsNotEnabled(); } @@ -348,21 +348,23 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) #endif try { - Exception commitException = null; - - lock (connection) + // If the connection is doomed, we can be certain that the + // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { - // If the connection is doomed, we can be certain that the - // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't - // attempt to commit it. - if (connection.IsConnectionDoomed) + lock (connection) { _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. _connection = null; - - enlistment.Aborted(SQL.ConnectionDoomed()); } - else + + enlistment.Aborted(SQL.ConnectionDoomed()); + } + else + { + Exception commitException; + lock (connection) { try { @@ -370,9 +372,10 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) ValidateActiveOnConnection(connection); _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. - _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event + _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + commitException = null; } catch (SqlException e) { @@ -391,42 +394,41 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) ADP.TraceExceptionWithoutRethrow(e); connection.DoomThisConnection(); } - if (commitException != null) + } + if (commitException != null) + { + // connection.ExecuteTransaction failed with exception + if (_internalTransaction.IsCommitted) { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) - { - // Even though we got an exception, the transaction - // was committed by the server. - enlistment.Committed(); - } - else if (_internalTransaction.IsAborted) - { - // The transaction was aborted, report that to - // SysTx. - enlistment.Aborted(commitException); - } - else - { - // The transaction is still active, we cannot - // know the state of the transaction. - enlistment.InDoubt(commitException); - } - - // We eat the exception. This is called on the SysTx - // thread, not the applications thread. If we don't - // eat the exception an UnhandledException will occur, - // causing the process to FailFast. + // Even though we got an exception, the transaction + // was committed by the server. + enlistment.Committed(); + } + else if (_internalTransaction.IsAborted) + { + // The transaction was aborted, report that to + // SysTx. + enlistment.Aborted(commitException); + } + else + { + // The transaction is still active, we cannot + // know the state of the transaction. + enlistment.InDoubt(commitException); } - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + // We eat the exception. This is called on the SysTx + // thread, not the applications thread. If we don't + // eat the exception an UnhandledException will occur, + // causing the process to FailFast. } - } - if (commitException == null) - { - // connection.ExecuteTransaction succeeded - enlistment.Committed(); + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) + { + // connection.ExecuteTransaction succeeded + enlistment.Committed(); + } } } catch (System.OutOfMemoryException e) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index e977641175..49c0883e3d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1235,7 +1235,7 @@ private void CompleteLogin(bool enlistOK) // for non-pooled connections, enlist in a distributed transaction // if present - and user specified to enlist - if (enlistOK && ConnectionOptions.Enlist) + if (enlistOK && ConnectionOptions.Enlist && RoutingInfo == null) { _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist; Transaction tx = ADP.GetCurrentTransaction(); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs index 4aebe4b518..0947d6d849 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1984,10 +1984,6 @@ internal static string SSPIGenerateError() { return StringsHelper.GetString(Strings.SQL_SSPIGenerateError); } - internal static string SqlServerBrowserNotAccessible() - { - return StringsHelper.GetString(Strings.SQL_SqlServerBrowserNotAccessible); - } internal static string KerberosTicketMissingError() { return StringsHelper.GetString(Strings.SQL_KerberosTicketMissingError); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 8217604c72..01e29ef579 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -39,6 +39,7 @@ internal struct SNIErrorDetails // and surfacing objects to the user. internal sealed partial class TdsParser { + private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); @@ -973,6 +974,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( int payloadOffset = 0; int payloadLength = 0; int option = payload[offset++]; + bool serverSupportsEncryption = false; while (option != (byte)PreLoginOptions.LASTOPT) { @@ -996,6 +998,13 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; case (int)PreLoginOptions.ENCRYPT: + if (tlsFirst) + { + // Can skip/ignore this option if we are doing TDS 8. + offset += 4; + break; + } + payloadOffset = payload[offset++] << 8 | payload[offset++]; payloadLength = payload[offset++] << 8 | payload[offset++]; @@ -1009,16 +1018,11 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( LOGIN } */ + // Any response other than NOT_SUP means the server supports encryption. + serverSupportsEncryption = serverOption != EncryptionOptions.NOT_SUP; + switch (_encryptionOption) { - case (EncryptionOptions.ON): - if (serverOption == EncryptionOptions.NOT_SUP) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - break; case (EncryptionOptions.OFF): if (serverOption == EncryptionOptions.OFF) { @@ -1034,8 +1038,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; case (EncryptionOptions.NOT_SUP): - if (!tlsFirst && serverOption == EncryptionOptions.REQ) + if (serverOption == EncryptionOptions.REQ) { + // Server requires encryption, but client does not support it. _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); ThrowExceptionAndWarning(_physicalStateObj); @@ -1043,22 +1048,16 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; default: - Debug.Fail("Invalid client encryption option detected"); + // Any other client option needs encryption + if (serverOption == EncryptionOptions.NOT_SUP) + { + _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); + _physicalStateObj.Dispose(); + ThrowExceptionAndWarning(_physicalStateObj); + } break; } - if (_encryptionOption == EncryptionOptions.ON || - _encryptionOption == EncryptionOptions.LOGIN) - { - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || - (_connHandler._accessTokenInBytes != null && !trustServerCert); - uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); - - EnableSsl(info, encrypt, integratedSecurity, serverCert); - } - break; case (int)PreLoginOptions.INSTANCE: @@ -1139,6 +1138,25 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( } } + if (_encryptionOption == EncryptionOptions.ON || + _encryptionOption == EncryptionOptions.LOGIN) + { + if (!serverSupportsEncryption) + { + _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); + _physicalStateObj.Dispose(); + ThrowExceptionAndWarning(_physicalStateObj); + } + + // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. + bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || + (_connHandler._accessTokenInBytes != null && !trustServerCert); + uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) + | (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); + + EnableSsl(info, encrypt, integratedSecurity, serverCert); + } + return PreLoginHandshakeStatus.Successful; } @@ -1516,20 +1534,17 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } else { - if (TdsParserStateObjectFactory.UseManagedSNI) { - // SNI error. Append additional error message info if available. - // + // SNI error. Append additional error message info if available and hasn't been included. string sniLookupMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber); - errorMessage = (errorMessage != string.Empty) ? - (sniLookupMessage + ": " + errorMessage) : - sniLookupMessage; + errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) + ? sniLookupMessage + : (sniLookupMessage + ": " + errorMessage); } else { // SNI error. Replace the entire message. - // errorMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber); // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code @@ -1538,6 +1553,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) errorMessage += LocalDBAPI.GetLocalDBMessage((int)details.nativeError); win32ErrorCode = 0; } + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", @@ -2752,7 +2768,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, // UTF8 collation if (env._newCollation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } else { @@ -4156,7 +4172,7 @@ internal bool TryProcessReturnValue(int length, TdsParserStateObject stateObj, o // UTF8 collation if (rec.collation.IsUTF8) { - rec.encoding = Encoding.UTF8; + rec.encoding = s_utf8EncodingWithoutBom; } else { @@ -4940,7 +4956,7 @@ private bool TryProcessTypeInfo(TdsParserStateObject stateObj, SqlMetaDataPriv c // UTF8 collation if (col.collation.IsUTF8) { - col.encoding = Encoding.UTF8; + col.encoding = s_utf8EncodingWithoutBom; } else { @@ -6070,6 +6086,19 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T } catch (Exception e) { + if (stateObj is not null) + { + // call to decrypt column keys has failed. The data wont be decrypted. + // Not setting the value to false, forces the driver to look for column value. + // Packet received from Key Vault will throws invalid token header. + if (stateObj.HasPendingData) + { + // Drain the pending data now if setting the HasPendingData to false. + // SqlDataReader.TryCloseInternal can not drain if HasPendingData = false. + DrainData(stateObj); + } + stateObj.HasPendingData = false; + } throw SQL.ColumnDecryptionFailed(columnName, null, e); } } @@ -10653,7 +10682,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars // Replace encoding if it is UTF8 if (metadata.collation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } _defaultCollation = metadata.collation; @@ -12538,8 +12567,7 @@ internal bool TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsPar return true; // No data } - Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL), - "Out of sync plp read request"); + Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL), "Out of sync plp read request"); Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!"); charsLeft = len; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 85f4588c8c..e9b86901f4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -758,13 +758,14 @@ private static string ToFriendlyName(this SslProtocols protocol) { name = "TLS 1.0"; } -#pragma warning disable CS0618 // Type or member is obsolete: SSL is depricated +// SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections +#pragma warning disable CS0618, CA5397 else if ((protocol & SslProtocols.Ssl3) == SslProtocols.Ssl3) { name = "SSL 3.0"; } else if ((protocol & SslProtocols.Ssl2) == SslProtocols.Ssl2) -#pragma warning restore CS0618 // Type or member is obsolete: SSL is depricated +#pragma warning restore CS0618, CA5397 { name = "SSL 2.0"; } @@ -784,9 +785,10 @@ private static string ToFriendlyName(this SslProtocols protocol) public static string GetProtocolWarning(this SslProtocols protocol) { string message = string.Empty; -#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated +// SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections +#pragma warning disable CS0618, CA5397 if ((protocol & (SslProtocols.Ssl2 | SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11)) != SslProtocols.None) -#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated +#pragma warning restore CS0618, CA5397 { message = StringsHelper.Format(Strings.SEC_ProtocolWarning, protocol.ToFriendlyName()); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index c17a6f9bd4..bb28f09010 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -447,13 +447,14 @@ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) } else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER)) { -#pragma warning disable CS0618 // Type or member is obsolete : SSL is depricated +// SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections +#pragma warning disable CS0618, CA5397 protocolVersion = (int)SslProtocols.Ssl3; } else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL2_SERVER)) { protocolVersion = (int)SslProtocols.Ssl2; -#pragma warning restore CS0618 // Type or member is obsolete : SSL is depricated +#pragma warning restore CS0618, CA5397 } else //if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_NONE)) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs index ccbc9db180..67a2aa5922 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -1446,6 +1446,15 @@ internal static string SNI_ERROR_1 { } } + /// + /// Looks up a localized string similar to Socket is null. + /// + internal static string SNI_ERROR_10 { + get { + return ResourceManager.GetString("SNI_ERROR_10", resourceCulture); + } + } + /// /// Looks up a localized string similar to Timeout error. /// @@ -1941,6 +1950,24 @@ internal static string SNI_PN1 { } } + /// + /// Looks up a localized string similar to . + /// + internal static string SNI_PN10 { + get { + return ResourceManager.GetString("SNI_PN10", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SQL Network Interfaces. + /// + internal static string SNI_PN11 { + get { + return ResourceManager.GetString("SNI_PN11", resourceCulture); + } + } + /// /// Looks up a localized string similar to Session Provider. /// @@ -1996,7 +2023,7 @@ internal static string SNI_PN7 { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to VIA Provider. /// internal static string SNI_PN8 { get { @@ -2005,7 +2032,7 @@ internal static string SNI_PN8 { } /// - /// Looks up a localized string similar to SQL Network Interfaces. + /// Looks up a localized string similar to CTAIP Provider. /// internal static string SNI_PN9 { get { @@ -3183,15 +3210,6 @@ internal static string SQL_SqlCommandCommandText { } } - /// - /// Looks up a localized string similar to Cannot connect to SQL Server Browser. Ensure SQL Server Browser has been started.. - /// - internal static string SQL_SqlServerBrowserNotAccessible { - get { - return ResourceManager.GetString("SQL_SqlServerBrowserNotAccessible", resourceCulture); - } - } - /// /// Looks up a localized string similar to Failed to generate SSPI context.. /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx index f268baa2de..70afac4e7d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx @@ -537,9 +537,6 @@ Cannot authenticate using Kerberos. Ensure Kerberos has been initialized on the client with 'kinit' and a Service Principal Name has been registered for the SQL Server to allow Kerberos authentication. - - Cannot connect to SQL Server Browser. Ensure SQL Server Browser has been started. - Invalid SSPI packet size. @@ -930,6 +927,9 @@ Associating port with I/O completion mechanism failed + + Socket is null + Timeout error @@ -1195,10 +1195,10 @@ TCP Provider - + VIA Provider - SQL Network Interfaces + CTAIP Provider .database.windows.net @@ -1941,4 +1941,10 @@ Encrypt=Strict is not supported when targeting .NET Standard 2.0. Use .NET Standard 2.1, .NET Framework, or .NET. + + + + + SQL Network Interfaces + diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 7a80ce32da..ba22087d50 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -8,14 +8,42 @@ Framework $(BaseProduct) Debug;Release; + + true + $(SigningKeyPath) + $(SigningKeyPath) + + + $(SigningKeyPath) + + + + + + + + + + $(MicrosoftDataSqlClientSniVersion) + All + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + - \ No newline at end of file + 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..cd49858ff6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -59,6 +59,14 @@ True False + + true + $(SigningKeyPath) + $(SigningKeyPath) + + + $(SigningKeyPath) + @@ -84,8 +92,8 @@ - + @@ -422,6 +430,9 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs + + Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs + Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs @@ -705,30 +716,20 @@ - - - $(SystemTextEncodingsWebVersion) - $(MicrosoftDataSqlClientSniVersion) All runtime; build; native; contentfiles; analyzers; buildtransitive - - $(AzureIdentityVersion) - - - $(MicrosoftIdentityClientVersion) - - - $(MicrosoftIdentityModelProtocolsOpenIdConnectVersion) - - - $(MicrosoftIdentityModelJsonWebTokensVersion) - - - $(SystemBuffersVersion) - + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index a79eb68411..06b1e04712 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -18,11 +18,10 @@ namespace Microsoft.Data.ProviderBase using SysTx = System.Transactions; internal abstract class DbConnectionInternal - { // V1.1.3300 - - + { private static int _objectTypeCount; internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); + private SysTx.TransactionCompletedEventHandler _transactionCompletedEventHandler = null; internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); @@ -900,15 +899,18 @@ internal void DetachTransaction(SysTx.Transaction transaction, bool isExplicitly // potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new // transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should // be the exception, not the rule. - lock (this) + // locking on anything other than the transaction object would lead to a thread deadlock with sys.Transaction.TransactionCompleted event. + lock (transaction) { // Detach if detach-on-end behavior, or if outer connection was closed - DbConnection owner = (DbConnection)Owner; - if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner) + DbConnection owner = Owner; + if (isExplicitlyReleasing || UnbindOnTransactionCompletion || owner is null) { SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction; if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction)) { + // We need to remove the transaction completed event handler to cease listening for the transaction to end. + currentEnlistedTransaction.TransactionCompleted -= _transactionCompletedEventHandler; EnlistedTransaction = null; @@ -947,7 +949,8 @@ void TransactionCompletedEvent(object sender, SysTx.TransactionEventArgs e) [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)] private void TransactionOutcomeEnlist(SysTx.Transaction transaction) { - transaction.TransactionCompleted += new SysTx.TransactionCompletedEventHandler(TransactionCompletedEvent); + _transactionCompletedEventHandler ??= new SysTx.TransactionCompletedEventHandler(TransactionCompletedEvent); + transaction.TransactionCompleted += _transactionCompletedEventHandler; } internal void SetInStasis() diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index 9017b84717..de26b79232 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -11,7 +11,7 @@ internal abstract class SqlColumnEncryptionEnclaveProvider { /// - internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); + internal abstract void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index fdfa0772ea..6922be9735 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -83,6 +83,11 @@ public sealed class SqlCommand : DbCommand, ICloneable /// Internal flag for testing purposes that forces all queries to internally end async calls. /// private static bool _forceInternalEndQuery = false; + + /// + /// Internal flag for testing purposes that forces one RetryableEnclaveQueryExecutionException during GenerateEnclavePackage + /// + private static bool _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; #endif internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; @@ -2649,7 +2654,7 @@ private IAsyncResult BeginExecuteReaderInternal(CommandBehavior behavior, AsyncC // back into pool when we should not. } - bool usedCache; + bool usedCache = false; Task writeTask = null; try { @@ -2782,11 +2787,7 @@ private bool TriggerInternalEndAndRetryIfNecessary(CommandBehavior behavior, obj // Remove the enrty from the cache since it was inconsistent. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); try { @@ -2827,6 +2828,26 @@ private bool TriggerInternalEndAndRetryIfNecessary(CommandBehavior behavior, obj } } + private void InvalidateEnclaveSession() + { + if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) + { + EnclaveDelegate.Instance.InvalidateEnclaveSession( + this._activeConnection.AttestationProtocol, + this._activeConnection.Parser.EnclaveType, + GetEnclaveSessionParameters(), + this.enclavePackage.EnclaveSession); + } + } + + private EnclaveSessionParameters GetEnclaveSessionParameters() + { + return new EnclaveSessionParameters( + this._activeConnection.DataSource, + this._activeConnection.EnclaveAttestationUrl, + this._activeConnection.Database); + } + private void BeginExecuteReaderInternalReadStage(TaskCompletionSource completion) { Debug.Assert(completion != null, "CompletionSource should not be null"); @@ -4143,7 +4164,8 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r asyncWrite, out describeParameterEncryptionNeeded, out fetchInputParameterEncryptionInfoTask, - out describeParameterEncryptionRpcOriginalRpcMap); + out describeParameterEncryptionRpcOriginalRpcMap, + inRetry); Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null, "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request."); @@ -4210,7 +4232,10 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption."); // Read the results of describe parameter encryption. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + ReadDescribeEncryptionParameterResults( + describeParameterEncryptionDataReader, + describeParameterEncryptionRpcOriginalRpcMap, + inRetry); #if DEBUG // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. @@ -4295,7 +4320,7 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r Debug.Assert(null == _stateObj, "non-null state object in PrepareForTransparentEncryption."); // Read the results of describe parameter encryption. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); #if DEBUG // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. if (_sleepAfterReadDescribeEncryptionParameterResults) @@ -4332,7 +4357,7 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r else { // For synchronous execution, read the results of describe parameter encryption here. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap); + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); } #if DEBUG @@ -4410,13 +4435,15 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r /// /// /// + /// Indicates if this is a retry from a failed call. /// private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, bool async, bool asyncWrite, out bool inputParameterEncryptionNeeded, out Task task, - out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) + out ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, + bool inRetry) { inputParameterEncryptionNeeded = false; task = null; @@ -4428,10 +4455,10 @@ private SqlDataReader TryFetchInputParameterEncryptionInfo(int timeout, SqlConnectionAttestationProtocol attestationProtocol = this._activeConnection.AttestationProtocol; string enclaveType = this._activeConnection.Parser.EnclaveType; - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); + EnclaveSessionParameters enclaveSessionParameters = GetEnclaveSessionParameters(); SqlEnclaveSession sqlEnclaveSession = null; - EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, true, out sqlEnclaveSession, out customData, out customDataLength); + EnclaveDelegate.Instance.GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, true, inRetry, out sqlEnclaveSession, out customData, out customDataLength); if (sqlEnclaveSession == null) { enclaveAttestationParameters = EnclaveDelegate.Instance.GetAttestationParameters(attestationProtocol, enclaveType, enclaveSessionParameters.AttestationUrl, customData, customDataLength); @@ -4691,7 +4718,8 @@ private void PrepareDescribeParameterEncryptionRequest(_SqlRPC originalRpcReques /// /// Resultset from calling to sp_describe_parameter_encryption /// Readonly dictionary with the map of parameter encryption rpc requests with the corresponding original rpc requests. - private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap) + /// Indicates if this is a retry from a failed call. + private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDictionary<_SqlRPC, _SqlRPC> describeParameterEncryptionRpcOriginalRpcMap, bool inRetry) { _SqlRPC rpc = null; int currentOrdinal = -1; @@ -4966,9 +4994,16 @@ private void ReadDescribeEncryptionParameterResults(SqlDataReader ds, ReadOnlyDi SqlConnectionAttestationProtocol attestationProtocol = this._activeConnection.AttestationProtocol; string enclaveType = this._activeConnection.Parser.EnclaveType; - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.CreateEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, attestationInfo, enclaveAttestationParameters, customData, customDataLength); + EnclaveDelegate.Instance.CreateEnclaveSession( + attestationProtocol, + enclaveType, + GetEnclaveSessionParameters(), + attestationInfo, + enclaveAttestationParameters, + customData, + customDataLength, + inRetry); enclaveAttestationParameters = null; attestationInfoRead = true; } @@ -5090,7 +5125,7 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) { - if (inRetry || async) + if (inRetry) { throw; } @@ -5099,22 +5134,16 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior // First invalidate the entry from the cache, so that we refresh our encryption MD. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, - enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); - return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, null, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); + return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); } catch (SqlException ex) { // We only want to retry once, so don't retry if we are already in retry. // If we didn't use the cache, we don't want to retry. - // The async retried are handled separately, handle only sync calls here. - if (inRetry || async || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) + if (inRetry || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) { throw; } @@ -5143,14 +5172,9 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior // First invalidate the entry from the cache, so that we refresh our encryption MD. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - if (ShouldUseEnclaveBasedWorkflow && this.enclavePackage != null) - { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); - EnclaveDelegate.Instance.InvalidateEnclaveSession(this._activeConnection.AttestationProtocol, this._activeConnection.Parser.EnclaveType, - enclaveSessionParameters, this.enclavePackage.EnclaveSession); - } + InvalidateEnclaveSession(); - return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, null, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); + return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); } } } @@ -5158,7 +5182,6 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior { return RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry); } - } #if DEBUG finally @@ -5273,9 +5296,15 @@ private void GenerateEnclavePackage() try { - EnclaveSessionParameters enclaveSessionParameters = new EnclaveSessionParameters(this._activeConnection.DataSource, this._activeConnection.EnclaveAttestationUrl, this._activeConnection.Database); +#if DEBUG + if (_forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage) + { + _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; + throw new EnclaveDelegate.RetryableEnclaveQueryExecutionException("testing", null); + } +#endif this.enclavePackage = EnclaveDelegate.Instance.GenerateEnclavePackage(attestationProtocol, keysToBeSentToEnclave, - this.CommandText, enclaveType, enclaveSessionParameters, _activeConnection, this); + this.CommandText, enclaveType, GetEnclaveSessionParameters(), _activeConnection, this); } catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) { @@ -5356,8 +5385,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi bool processFinallyBlock = true; bool decrementAsyncCountOnFailure = false; - // If we are in retry, don't increment the Async count. This should have already been set. - if (async && !inRetry) + if (async) { _activeConnection.GetOpenTdsConnection().IncrementAsyncCount(); decrementAsyncCountOnFailure = true; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index bf7f86c3a7..e11ec36e79 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -192,17 +192,17 @@ public Byte[] Promote() ValidateActiveOnConnection(connection); connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, IsolationLevel.Unspecified, _internalTransaction, true); - returnValue = _connection.PromotedDTCToken; + returnValue = connection.PromotedDTCToken; // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. - if (_connection.IsGlobalTransaction) + if (connection.IsGlobalTransaction) { if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) { throw SQL.UnsupportedSysTxForGlobalTransactions(); } - if (!_connection.IsGlobalTransactionsEnabledForServer) + if (!connection.IsGlobalTransactionsEnabledForServer) { throw SQL.GlobalTransactionsNotEnabled(); } @@ -387,7 +387,6 @@ public void SinglePhaseCommit(SysTx.SinglePhaseEnlistment enlistment) Debug.Assert(null != enlistment, "null enlistment?"); SqlInternalConnection connection = GetValidConnection(); - if (null != connection) { SqlConnection usersConnection = connection.Connection; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index ee8d211e7b..748483b49d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1500,7 +1500,7 @@ private void CompleteLogin(bool enlistOK) // for non-pooled connections, enlist in a distributed transaction // if present - and user specified to enlist - if (enlistOK && ConnectionOptions.Enlist) + if (enlistOK && ConnectionOptions.Enlist && _routingInfo == null) { _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist; SysTx.Transaction tx = ADP.GetCurrentTransaction(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 70673ef343..264e132009 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -32,6 +32,7 @@ namespace Microsoft.Data.SqlClient // and surfacing objects to the user. sealed internal class TdsParser { + private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static int _objectTypeCount; // EventSource Counter private readonly SqlClientLogger _logger = new SqlClientLogger(); @@ -1392,6 +1393,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( int payloadOffset = 0; int payloadLength = 0; int option = payload[offset++]; + bool serverSupportsEncryption = false; + bool serverSupportsCTAIP = false; while (option != (byte)PreLoginOptions.LASTOPT) { @@ -1415,29 +1418,33 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; case (int)PreLoginOptions.ENCRYPT: + if (tlsFirst) + { + // Can skip/ignore this option if we are doing TDS 8. + offset += 4; + break; + } + payloadOffset = payload[offset++] << 8 | payload[offset++]; payloadLength = payload[offset++] << 8 | payload[offset++]; EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset]; /* internal enum EncryptionOptions { - OFF, - ON, - NOT_SUP, - REQ, - LOGIN - } */ + OFF, + ON, + NOT_SUP, + REQ, + LOGIN, + OPTIONS_MASK = 0x3f, + CTAIP = 0x40, + CLIENT_CERT = 0x80, + } */ + + // Any response other than NOT_SUP means the server supports encryption. + serverSupportsEncryption = (serverOption & EncryptionOptions.OPTIONS_MASK) != EncryptionOptions.NOT_SUP; + switch (_encryptionOption & EncryptionOptions.OPTIONS_MASK) { - case (EncryptionOptions.ON): - if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.NOT_SUP) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - break; - case (EncryptionOptions.OFF): if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.OFF) { @@ -1453,8 +1460,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; case (EncryptionOptions.NOT_SUP): - if (!tlsFirst && (serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) + if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ) { + // Server requires encryption, but client does not support it. _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0)); _physicalStateObj.Dispose(); ThrowExceptionAndWarning(_physicalStateObj); @@ -1463,37 +1471,20 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( break; default: - Debug.Fail("Invalid client encryption option detected"); + // Any other client option needs encryption + if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.NOT_SUP) + { + _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); + _physicalStateObj.Dispose(); + ThrowExceptionAndWarning(_physicalStateObj); + } + break; } // Check if the server will accept CTAIP. // - if ((_encryptionOption & EncryptionOptions.CTAIP) != 0 && - (serverOption & EncryptionOptions.CTAIP) == 0) - { - _physicalStateObj.AddError(new SqlError(TdsEnums.CTAIP_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.CTAIPNotSupportedByServer(), "", 0)); - _physicalStateObj.Dispose(); - ThrowExceptionAndWarning(_physicalStateObj); - } - - if ((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON || - (_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN) - { - - if (serverCallback != null) - { - trustServerCert = true; - } - - // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. - bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert); - - UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | (is2005OrLater && (_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0 ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); - - EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename, serverCallback, clientCallback); - } + serverSupportsCTAIP = (serverOption & EncryptionOptions.CTAIP) != 0; break; @@ -1576,6 +1567,37 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( } } + if ((_encryptionOption & EncryptionOptions.CTAIP) != 0 && !serverSupportsCTAIP) + { + _physicalStateObj.AddError(new SqlError(TdsEnums.CTAIP_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.CTAIPNotSupportedByServer(), "", 0)); + _physicalStateObj.Dispose(); + ThrowExceptionAndWarning(_physicalStateObj); + } + + if ((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON || + (_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN) + { + if (!serverSupportsEncryption) + { + _physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0)); + _physicalStateObj.Dispose(); + ThrowExceptionAndWarning(_physicalStateObj); + } + + if (serverCallback != null) + { + trustServerCert = true; + } + + // Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server. + bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert); + + uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) + | (is2005OrLater && (_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0 ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); + + EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename, serverCallback, clientCallback); + } + return PreLoginHandshakeStatus.Successful; } @@ -3200,7 +3222,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, // UTF8 collation if (env._newCollation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } else { @@ -4718,7 +4740,7 @@ internal bool TryProcessReturnValue(int length, if (rec.collation.IsUTF8) { // UTF8 collation - rec.encoding = Encoding.UTF8; + rec.encoding = s_utf8EncodingWithoutBom; } else { @@ -5615,7 +5637,7 @@ private bool TryProcessTypeInfo(TdsParserStateObject stateObj, SqlMetaDataPriv c if (col.collation.IsUTF8) { // UTF8 collation - col.encoding = Encoding.UTF8; + col.encoding = s_utf8EncodingWithoutBom; } else { @@ -6884,6 +6906,19 @@ internal bool TryReadSqlValue(SqlBuffer value, } catch (Exception e) { + if (stateObj is not null) + { + // call to decrypt column keys has failed. The data wont be decrypted. + // Not setting the value to false, forces the driver to look for column value. + // Packet received from Key Vault will throws invalid token header. + if (stateObj._pendingData) + { + // Drain the pending data now if setting the HasPendingData to false. + // SqlDataReader.TryCloseInternal can not drain if HasPendingData = false. + DrainData(stateObj); + } + stateObj._pendingData = false; + } throw SQL.ColumnDecryptionFailed(columnName, null, e); } } @@ -10017,10 +10052,10 @@ internal Task TdsExecuteRPC(SqlCommand cmd, _SqlRPC[] rpcArray, int timeout, boo // Options WriteShort((short)rpcext.options, stateObj); - } - byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null; - WriteEnclaveInfo(stateObj, enclavePackage); + byte[] enclavePackage = cmd.enclavePackage != null ? cmd.enclavePackage.EnclavePackageBytes : null; + WriteEnclaveInfo(stateObj, enclavePackage); + } // Stream out parameters int parametersLength = rpcext.userParamCount + rpcext.systemParamCount; @@ -11636,7 +11671,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars // Replace encoding if it is UTF8 if (metadata.collation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } _defaultCollation = metadata.collation; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index b30f54f8e5..f049de08f3 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -8182,7 +8182,7 @@ internal static string SNI_ERROR_1 { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Socket is null. /// internal static string SNI_ERROR_10 { get { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index d9aa92e08b..9814565852 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -3805,7 +3805,7 @@ Zuordnung des Anschlusses mit E/A-Abschlussmechanismus fehlgeschlagen - + Socket ist NULL. Timeoutfehler @@ -4635,4 +4635,4 @@ Die Plattform "{0}" wird bei .NET Framework nicht unterstützt. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index bb98c84037..ed56be833b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -3805,7 +3805,7 @@ Error al completar el mecanismo de asociación de puerto con E/S - + El socket es null Error de tiempo de espera @@ -4635,4 +4635,4 @@ La plataforma '{0}' no se admite cuando el destino es .NET Framework. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index 3e83f37ccf..f1c2a8f54f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -3805,7 +3805,7 @@ Échec de l'association du port avec le mécanisme de terminaison d'E/S - + Le socket est nul Erreur de délai d'attente @@ -4635,4 +4635,4 @@ La plateforme '{0}' n’est pas prise en charge lors du ciblage de .NET Framework. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index 34bdf447e8..3af480b805 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -3805,7 +3805,7 @@ Associazione della porta al meccanismo di completamento I/O non riuscita - + Il socket è null Errore di timeout @@ -4635,4 +4635,4 @@ La piattaforma '{0}' non è supportata quando la destinazione è .NET Framework. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index b28b582fe6..b20c2434ec 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -3805,7 +3805,7 @@ ポートと I/O 完了メカニズムの関連付けに失敗しました - + ソケットは null です タイムアウト エラー @@ -4635,4 +4635,4 @@ .NET Framework を対象とする場合、'{0}' プラットフォームはサポートされません。 - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index 5c3755a573..ff3b07f2aa 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -3805,7 +3805,7 @@ 포트와 입/출력 완료 메커니즘의 연결이 실패했습니다. - + 소켓이 null입니다. 시간 초과 오류입니다. @@ -4635,4 +4635,4 @@ .NET Framework를 대상으로 지정할 때는 '{0}' 플랫폼이 지원되지 않습니다. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index 353ef45af7..6f575dcb01 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -3805,7 +3805,7 @@ Falha ao associar porta e mecanismo de conclusão de E/S - + O soquete é nulo Erro de tempo limite @@ -4635,4 +4635,4 @@ A plataforma '{0}' não é suportada ao direcionar o .NET Framework. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index 85627eae31..88318d6452 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -3805,7 +3805,7 @@ Associating port with I/O completion mechanism failed - + Socket is null Timeout error diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index a8d3c14134..93d1b4f376 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -3805,7 +3805,7 @@ Сбой при сопоставлении порта механизму завершения ввода-вывода - + Сокет имеет значение NULL Ошибка времени ожидания @@ -4635,4 +4635,4 @@ Платформа "{0}" не поддерживается, если целью является .NET Framework. - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index f71303097d..8d42310c50 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -3805,7 +3805,7 @@ 端口与 I/O 关联的完成机制失败 - + 套接字为 null 超时错误 @@ -4635,4 +4635,4 @@ 面向 .NET Framework 时,不支持 "{0}" 平台。 - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index 8fab7073e5..70ba001f41 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -3805,7 +3805,7 @@ 關聯連接埠與 I/O 完成機制失敗 - + 通訊端為 null 逾時錯誤 @@ -4635,4 +4635,4 @@ 以.NET Framework 為目標時,不支援 '{0}' 平台。 - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ActivityCorrelator.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ActivityCorrelator.cs index ef6b9b6cd2..5823921abc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ActivityCorrelator.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ActivityCorrelator.cs @@ -19,16 +19,14 @@ internal sealed class ActivityId internal readonly Guid Id; internal readonly uint Sequence; - internal ActivityId(uint sequence) + internal ActivityId(Guid? currentActivityId, uint sequence = 1) { - this.Id = Guid.NewGuid(); - this.Sequence = sequence; + Id = currentActivityId ?? Guid.NewGuid(); + Sequence = sequence; } public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.Id, this.Sequence); - } + => string.Format(CultureInfo.InvariantCulture, "{0}:{1}", Id, Sequence); } // Declare the ActivityId which will be stored in TLS. The Id is unique for each thread. @@ -40,27 +38,12 @@ public override string ToString() /// /// Get the current ActivityId /// - internal static ActivityId Current - { - get - { - if (t_tlsActivity == null) - { - t_tlsActivity = new ActivityId(1); - } - return t_tlsActivity; - } - } + internal static ActivityId Current => t_tlsActivity ??= new ActivityId(null); /// /// Increment the sequence number and generate the new ActivityId /// /// ActivityId - internal static ActivityId Next() - { - t_tlsActivity = new ActivityId( (t_tlsActivity?.Sequence ?? 0) + 1); - - return t_tlsActivity; - } + internal static ActivityId Next() => t_tlsActivity = new ActivityId(t_tlsActivity?.Id, (t_tlsActivity?.Sequence ?? 0) + 1); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index f8a31231c0..7e21441852 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -816,10 +816,18 @@ internal static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(s { return DbConnectionStringDefaults.Encrypt; } + else if(value is SqlConnectionEncryptOption eValue) + { + return eValue; + } else if (value is string sValue) { return SqlConnectionEncryptOption.Parse(sValue); } + else if(value is bool bValue) + { + return SqlConnectionEncryptOption.Parse(bValue); + } throw ADP.InvalidConnectionOptionValue(keyword); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs index 6e907d26e1..c3c34c702b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Xml; namespace Microsoft.Data.ProviderBase { @@ -499,9 +500,14 @@ private void LoadDataSetFromXml(Stream XmlStream) { _metaDataCollectionsDataSet = new DataSet { - Locale = System.Globalization.CultureInfo.InvariantCulture + Locale = CultureInfo.InvariantCulture + }; + XmlReaderSettings settings = new() + { + XmlResolver = null }; - _metaDataCollectionsDataSet.ReadXml(XmlStream); + using XmlReader reader = XmlReader.Create(XmlStream, settings); + _metaDataCollectionsDataSet.ReadXml(reader); } protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index 573c36ee55..73fce50774 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Concurrent; -using System.Security; +using System.Linq; +using System.Runtime.Caching; +using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; using Azure.Core; @@ -22,12 +25,16 @@ public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationPro /// The purpose of this cache is to allow re-use of Access Tokens fetched for a user interactively or with any other mode /// to avoid interactive authentication request every-time, within application scope making use of MSAL's userTokenCache. /// - private static ConcurrentDictionary s_pcaMap - = new ConcurrentDictionary(); + private static readonly MemoryCache s_accountPwCache = new(nameof(ActiveDirectoryAuthenticationProvider)); + private static readonly ConcurrentDictionary s_pcaMap = new(); + private static readonly ConcurrentDictionary s_tokenCredentialMap = new(); + private static SemaphoreSlim s_pcaMapModifierSemaphore = new(1, 1); + private static SemaphoreSlim s_tokenCredentialMapModifierSemaphore = new(1, 1); + private static readonly int s_accountPwCacheTtlInHours = 2; private static readonly string s_nativeClientRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"; private static readonly string s_defaultScopeSuffix = "/.default"; private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; - private readonly SqlClientLogger _logger = new SqlClientLogger(); + private readonly SqlClientLogger _logger = new(); private Func _deviceCodeFlowCallback; private ICustomWebUi _customWebUI = null; private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; @@ -61,6 +68,11 @@ public static void ClearUserTokenCache() { s_pcaMap.Clear(); } + + if (!s_tokenCredentialMap.IsEmpty) + { + s_tokenCredentialMap.Clear(); + } } /// @@ -115,7 +127,11 @@ public override async Task AcquireTokenAsync(SqlAuthenti CancellationTokenSource cts = new CancellationTokenSource(); // Use Connection timeout value to cancel token acquire request after certain period of time. - cts.CancelAfter(parameters.ConnectionTimeout * 1000); // Convert to milliseconds + int timeout = parameters.ConnectionTimeout * 1000; + if (timeout > 0) // if ConnectionTimeout is 0 or the millis overflows an int, no need to set CancelAfter + { + cts.CancelAfter(timeout); // Convert to milliseconds + } string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; @@ -136,50 +152,40 @@ public override async Task AcquireTokenAsync(SqlAuthenti * More information: https://docs.microsoft.com/azure/active-directory/develop/msal-client-application-configuration **/ - int seperatorIndex = parameters.Authority.LastIndexOf('/'); - string authority = parameters.Authority.Remove(seperatorIndex + 1); - string audience = parameters.Authority.Substring(seperatorIndex + 1); + int separatorIndex = parameters.Authority.LastIndexOf('/'); + string authority = parameters.Authority.Remove(separatorIndex + 1); + string audience = parameters.Authority.Substring(separatorIndex + 1); string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault) { - DefaultAzureCredentialOptions defaultAzureCredentialOptions = new() - { - AuthorityHost = new Uri(authority), - SharedTokenCacheTenantId = audience, - VisualStudioCodeTenantId = audience, - VisualStudioTenantId = audience, - ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications. - }; - - // Optionally set clientId when available - if (clientId is not null) - { - defaultAzureCredentialOptions.ManagedIdentityClientId = clientId; - defaultAzureCredentialOptions.SharedTokenCacheUsername = clientId; - } - AccessToken accessToken = await new DefaultAzureCredential(defaultAzureCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + // Cache DefaultAzureCredenial based on scope, authority, audience, and clientId + TokenCredentialKey tokenCredentialKey = new(typeof(DefaultAzureCredential), authority, scope, audience, clientId); + AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } - TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions() { AuthorityHost = new Uri(authority) }; + TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(authority) }; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryMSI) { - AccessToken accessToken = await new ManagedIdentityCredential(clientId, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + // Cache ManagedIdentityCredential based on scope, authority, and clientId + TokenCredentialKey tokenCredentialKey = new(typeof(ManagedIdentityCredential), authority, scope, string.Empty, clientId); + AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Managed Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } - AuthenticationResult result; + AuthenticationResult result = null; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) { - AccessToken accessToken = await new ClientSecretCredential(audience, parameters.UserId, parameters.Password, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + // Cache ClientSecretCredential based on scope, authority, audience, and clientId + TokenCredentialKey tokenCredentialKey = new(typeof(ClientSecretCredential), authority, scope, audience, clientId); + AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, parameters.Password, tokenRequestContext, cts.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); } - /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend @@ -195,7 +201,7 @@ public override async Task AcquireTokenAsync(SqlAuthenti redirectUri = "http://localhost"; } #endif - PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId + PublicClientAppKey pcaKey = new(parameters.Authority, redirectUri, _applicationClientId #if NETFRAMEWORK , _iWin32WindowFunc #endif @@ -204,86 +210,86 @@ public override async Task AcquireTokenAsync(SqlAuthenti #endif ); - IPublicClientApplication app = GetPublicClientAppInstance(pcaKey); + IPublicClientApplication app = await GetPublicClientAppInstanceAsync(pcaKey, cts.Token).ConfigureAwait(false); if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - if (!string.IsNullOrEmpty(parameters.UserId)) - { - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) - .WithCorrelationId(parameters.ConnectionId) - .WithUsername(parameters.UserId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - else - { - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) - { - result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); + result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || - parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) - { - // Fetch available accounts from 'app' instance - System.Collections.Generic.IEnumerator accounts = (await app.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator(); - - IAccount account = default; - if (accounts.MoveNext()) + if (null == result) { if (!string.IsNullOrEmpty(parameters.UserId)) { - do - { - IAccount currentVal = accounts.Current; - if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0) - { - account = currentVal; - break; - } - } - while (accounts.MoveNext()); + result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) + .WithCorrelationId(parameters.ConnectionId) + .WithUsername(parameters.UserId) + .ExecuteAsync(cancellationToken: cts.Token) + .ConfigureAwait(false); } else { - account = accounts.Current; + result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) + .WithCorrelationId(parameters.ConnectionId) + .ExecuteAsync(cancellationToken: cts.Token) + .ConfigureAwait(false); } + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn); + } + } + else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) + { + string pwCacheKey = GetAccountPwCacheKey(parameters); + object previousPw = s_accountPwCache.Get(pwCacheKey); + byte[] currPwHash = GetHash(parameters.Password); + + if (null != previousPw && + previousPw is byte[] previousPwBytes && + // Only get the cached token if the current password hash matches the previously used password hash + AreEqual(currPwHash, previousPwBytes)) + { + result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); } - if (null != account) + if (null == result) { - try + result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) + .WithCorrelationId(parameters.ConnectionId) + .ExecuteAsync(cancellationToken: cts.Token) + .ConfigureAwait(false); + + // We cache the password hash to ensure future connection requests include a validated password + // when we check for a cached MSAL account. Otherwise, a connection request with the same username + // against the same tenant could succeed with an invalid password when we re-use the cached token. + if (!s_accountPwCache.Add(pwCacheKey, GetHash(parameters.Password), DateTime.UtcNow.AddHours(s_accountPwCacheTtlInHours))) { - // If 'account' is available in 'app', we use the same to acquire token silently. - // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent - result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - catch (MsalUiRequiredException) - { - // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, - // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), - // or the user needs to perform two factor authentication. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); + s_accountPwCache.Remove(pwCacheKey); + s_accountPwCache.Add(pwCacheKey, GetHash(parameters.Password), DateTime.UtcNow.AddHours(s_accountPwCacheTtlInHours)); } + + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); } - else + } + else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || + parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) + { + try + { + result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); + } + catch (MsalUiRequiredException) + { + // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, + // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), + // or the user needs to perform two factor authentication. + result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); + } + + if (null == result) { // If no existing 'account' is found, we request user to sign in interactively. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts).ConfigureAwait(false); + result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); } } @@ -296,14 +302,55 @@ public override async Task AcquireTokenAsync(SqlAuthenti return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); } - private async Task AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, - SqlAuthenticationMethod authenticationMethod, CancellationTokenSource cts) + private static async Task TryAcquireTokenSilent(IPublicClientApplication app, SqlAuthenticationParameters parameters, + string[] scopes, CancellationTokenSource cts) + { + AuthenticationResult result = null; + + // Fetch available accounts from 'app' instance + System.Collections.Generic.IEnumerator accounts = (await app.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator(); + + IAccount account = default; + if (accounts.MoveNext()) + { + if (!string.IsNullOrEmpty(parameters.UserId)) + { + do + { + IAccount currentVal = accounts.Current; + if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0) + { + account = currentVal; + break; + } + } + while (accounts.MoveNext()); + } + else + { + account = accounts.Current; + } + } + + if (null != account) + { + // If 'account' is available in 'app', we use the same to acquire token silently. + // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent + result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token).ConfigureAwait(false); + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); + } + + return result; + } + + private static async Task AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, + SqlAuthenticationMethod authenticationMethod, CancellationTokenSource cts, ICustomWebUi customWebUI, Func deviceCodeFlowCallback) { try { if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) { - CancellationTokenSource ctsInteractive = new CancellationTokenSource(); + CancellationTokenSource ctsInteractive = new(); #if NETCOREAPP /* * On .NET Core, MSAL will start the system browser as a separate process. MSAL does not have control over this browser, @@ -316,11 +363,11 @@ private async Task AcquireTokenInteractiveDeviceFlowAsync( */ ctsInteractive.CancelAfter(180000); #endif - if (_customWebUI != null) + if (customWebUI != null) { return await app.AcquireTokenInteractive(scopes) .WithCorrelationId(connectionId) - .WithCustomWebUi(_customWebUI) + .WithCustomWebUi(customWebUI) .WithLoginHint(userId) .ExecuteAsync(ctsInteractive.Token) .ConfigureAwait(false); @@ -354,7 +401,7 @@ private async Task AcquireTokenInteractiveDeviceFlowAsync( else { AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => _deviceCodeFlowCallback(deviceCodeResult)) + deviceCodeResult => deviceCodeFlowCallback(deviceCodeResult)) .WithCorrelationId(connectionId) .ExecuteAsync(cancellationToken: cts.Token) .ConfigureAwait(false); @@ -397,57 +444,179 @@ public Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirec => _acquireAuthorizationCodeAsyncCallback.Invoke(authorizationUri, redirectUri, cancellationToken); } - private IPublicClientApplication GetPublicClientAppInstance(PublicClientAppKey publicClientAppKey) + private async Task GetPublicClientAppInstanceAsync(PublicClientAppKey publicClientAppKey, CancellationToken cancellationToken) { if (!s_pcaMap.TryGetValue(publicClientAppKey, out IPublicClientApplication clientApplicationInstance)) { - clientApplicationInstance = CreateClientAppInstance(publicClientAppKey); - s_pcaMap.TryAdd(publicClientAppKey, clientApplicationInstance); + await s_pcaMapModifierSemaphore.WaitAsync(cancellationToken); + try + { + // Double-check in case another thread added it while we waited for the semaphore + if (!s_pcaMap.TryGetValue(publicClientAppKey, out clientApplicationInstance)) + { + clientApplicationInstance = CreateClientAppInstance(publicClientAppKey); + s_pcaMap.TryAdd(publicClientAppKey, clientApplicationInstance); + } + } + finally + { + s_pcaMapModifierSemaphore.Release(); + } } + return clientApplicationInstance; } + private static async Task GetTokenAsync(TokenCredentialKey tokenCredentialKey, string secret, + TokenRequestContext tokenRequestContext, CancellationToken cancellationToken) + { + if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out TokenCredentialData tokenCredentialInstance)) + { + await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); + try + { + // Double-check in case another thread added it while we waited for the semaphore + if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out tokenCredentialInstance)) + { + tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); + s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); + } + } + finally + { + s_tokenCredentialMapModifierSemaphore.Release(); + } + } + + if (!AreEqual(tokenCredentialInstance._secretHash, GetHash(secret))) + { + // If the secret hash has changed, we need to remove the old token credential instance and create a new one. + await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); + try + { + s_tokenCredentialMap.TryRemove(tokenCredentialKey, out _); + tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); + s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); + } + finally + { + s_tokenCredentialMapModifierSemaphore.Release(); + } + } + + return await tokenCredentialInstance._tokenCredential.GetTokenAsync(tokenRequestContext, cancellationToken); + } + + private static string GetAccountPwCacheKey(SqlAuthenticationParameters parameters) + { + return parameters.Authority + "+" + parameters.UserId; + } + + private static byte[] GetHash(string input) + { + byte[] unhashedBytes = Encoding.Unicode.GetBytes(input); + SHA256 sha256 = SHA256.Create(); + byte[] hashedBytes = sha256.ComputeHash(unhashedBytes); + return hashedBytes; + } + + private static bool AreEqual(byte[] a1, byte[] a2) + { + if (ReferenceEquals(a1, a2)) + { + return true; + } + else if (a1 is null || a2 is null) + { + return false; + } + else if (a1.Length != a2.Length) + { + return false; + } + + return a1.AsSpan().SequenceEqual(a2.AsSpan()); + } + private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) { - IPublicClientApplication publicClientApplication; + PublicClientApplicationBuilder builder = PublicClientApplicationBuilder + .CreateWithApplicationOptions(new PublicClientApplicationOptions + { + ClientId = publicClientAppKey._applicationClientId, + ClientName = Common.DbConnectionStringDefaults.ApplicationName, + ClientVersion = Common.ADP.GetAssemblyVersion().ToString(), + RedirectUri = publicClientAppKey._redirectUri, + }) + .WithAuthority(publicClientAppKey._authority); + + #if NETFRAMEWORK + if (_iWin32WindowFunc is not null) + { + builder.WithParentActivityOrWindow(_iWin32WindowFunc); + } + #endif + #if NETSTANDARD + if (_parentActivityOrWindowFunc is not null) + { + builder.WithParentActivityOrWindow(_parentActivityOrWindowFunc); + } + #endif -#if NETSTANDARD - if (_parentActivityOrWindowFunc != null) + return builder.Build(); + } + + private static TokenCredentialData CreateTokenCredentialInstance(TokenCredentialKey tokenCredentialKey, string secret) + { + if (tokenCredentialKey._tokenCredentialType == typeof(DefaultAzureCredential)) + { + DefaultAzureCredentialOptions defaultAzureCredentialOptions = new() + { + AuthorityHost = new Uri(tokenCredentialKey._authority), + SharedTokenCacheTenantId = tokenCredentialKey._audience, + VisualStudioCodeTenantId = tokenCredentialKey._audience, + VisualStudioTenantId = tokenCredentialKey._audience, + ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications. + }; + + // Optionally set clientId when available + if (tokenCredentialKey._clientId is not null) + { + defaultAzureCredentialOptions.ManagedIdentityClientId = tokenCredentialKey._clientId; + defaultAzureCredentialOptions.SharedTokenCacheUsername = tokenCredentialKey._clientId; + defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; + } + + return new TokenCredentialData(new DefaultAzureCredential(defaultAzureCredentialOptions), GetHash(secret)); + } + + TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; + + if (tokenCredentialKey._tokenCredentialType == typeof(ManagedIdentityCredential)) { - publicClientApplication = PublicClientApplicationBuilder.Create(publicClientAppKey._applicationClientId) - .WithAuthority(publicClientAppKey._authority) - .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) - .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) - .WithRedirectUri(publicClientAppKey._redirectUri) - .WithParentActivityOrWindow(_parentActivityOrWindowFunc) - .Build(); + return new TokenCredentialData(new ManagedIdentityCredential(tokenCredentialKey._clientId, tokenCredentialOptions), GetHash(secret)); } -#endif -#if NETFRAMEWORK - if (_iWin32WindowFunc != null) + else if (tokenCredentialKey._tokenCredentialType == typeof(ClientSecretCredential)) { - publicClientApplication = PublicClientApplicationBuilder.Create(publicClientAppKey._applicationClientId) - .WithAuthority(publicClientAppKey._authority) - .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) - .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) - .WithRedirectUri(publicClientAppKey._redirectUri) - .WithParentActivityOrWindow(_iWin32WindowFunc) - .Build(); + return new TokenCredentialData(new ClientSecretCredential(tokenCredentialKey._audience, tokenCredentialKey._clientId, secret, tokenCredentialOptions), GetHash(secret)); } -#endif -#if !NETCOREAPP - else -#endif + else if (tokenCredentialKey._tokenCredentialType == typeof(WorkloadIdentityCredential)) { - publicClientApplication = PublicClientApplicationBuilder.Create(publicClientAppKey._applicationClientId) - .WithAuthority(publicClientAppKey._authority) - .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) - .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) - .WithRedirectUri(publicClientAppKey._redirectUri) - .Build(); + // The WorkloadIdentityCredentialOptions object initialization populates its instance members + // from the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, + // and AZURE_ADDITIONALLY_ALLOWED_TENANTS. AZURE_CLIENT_ID may be overridden by the User Id. + WorkloadIdentityCredentialOptions options = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; + + if (tokenCredentialKey._clientId is not null) + { + options.ClientId = tokenCredentialKey._clientId; + } + + return new TokenCredentialData(new WorkloadIdentityCredential(options), GetHash(secret)); } - return publicClientApplication; + // This should never be reached, but if it is, throw an exception that will be noticed during development + throw new ArgumentException(nameof(ActiveDirectoryAuthenticationProvider)); } internal class PublicClientAppKey @@ -509,5 +678,52 @@ public override int GetHashCode() => Tuple.Create(_authority, _redirectUri, _app #endif ).GetHashCode(); } + + internal class TokenCredentialData + { + public TokenCredential _tokenCredential; + public byte[] _secretHash; + + public TokenCredentialData(TokenCredential tokenCredential, byte[] secretHash) + { + _tokenCredential = tokenCredential; + _secretHash = secretHash; + } + } + + internal class TokenCredentialKey + { + public readonly Type _tokenCredentialType; + public readonly string _authority; + public readonly string _scope; + public readonly string _audience; + public readonly string _clientId; + + public TokenCredentialKey(Type tokenCredentialType, string authority, string scope, string audience, string clientId) + { + _tokenCredentialType = tokenCredentialType; + _authority = authority; + _scope = scope; + _audience = audience; + _clientId = clientId; + } + + public override bool Equals(object obj) + { + if (obj != null && obj is TokenCredentialKey tcKey) + { + return string.CompareOrdinal(nameof(_tokenCredentialType), nameof(tcKey._tokenCredentialType)) == 0 + && string.CompareOrdinal(_authority, tcKey._authority) == 0 + && string.CompareOrdinal(_scope, tcKey._scope) == 0 + && string.CompareOrdinal(_audience, tcKey._audience) == 0 + && string.CompareOrdinal(_clientId, tcKey._clientId) == 0 + ; + } + return false; + } + + public override int GetHashCode() => Tuple.Create(_tokenCredentialType, _authority, _scope, _audience, _clientId).GetHashCode(); + } + } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 433347ef10..07848e651c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -65,9 +65,9 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase #region Internal methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); + GetEnclaveSessionHelper(enclaveSessionParameters, generateCustomData, isRetry, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs index d1e6c71527..db1bdea90f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs @@ -20,10 +20,11 @@ internal sealed partial class EnclaveDelegate /// The set of parameters required for enclave session. /// attestation info from SQL Server /// attestation parameters - /// A set of extra data needed for attestating the enclave. - /// The length of the extra data needed for attestating the enclave. + /// A set of extra data needed for attesting the enclave. + /// The length of the extra data needed for attesting the enclave. + /// Indicates if this is a retry from a failed call. internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) + byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength, bool isRetry) { lock (_lock) { @@ -32,6 +33,7 @@ internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationP sqlColumnEncryptionEnclaveProvider.GetEnclaveSession( enclaveSessionParameters, generateCustomData: false, + isRetry: isRetry, sqlEnclaveSession: out SqlEnclaveSession sqlEnclaveSession, counter: out _, customData: out _, @@ -60,15 +62,15 @@ internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationP } } - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) + internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) { - GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out _, out customData, out customDataLength, throwIfNull: false); + GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, generateCustomData, isRetry, out sqlEnclaveSession, out _, out customData, out customDataLength, throwIfNull: false); } - private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) + private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) { SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); + sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(enclaveSessionParameters, generateCustomData, isRetry, out sqlEnclaveSession, out counter, out customData, out customDataLength); if (throwIfNull && sqlEnclaveSession == null) { @@ -143,6 +145,7 @@ internal EnclavePackage GenerateEnclavePackage(SqlConnectionAttestationProtocol enclaveType, enclaveSessionParameters, generateCustomData: false, + isRetry: false, sqlEnclaveSession: out sqlEnclaveSession, counter: out counter, customData: out _, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs index 7b43c8cb66..461bfdf6d7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs @@ -24,15 +24,16 @@ internal byte[] GetSerializedAttestationParameters( /// The set of parameters required for enclave session. /// attestation info from SQL Server /// attestation parameters - /// A set of extra data needed for attestating the enclave. - /// The length of the extra data needed for attestating the enclave. + /// A set of extra data needed for attesting the enclave. + /// The length of the extra data needed for attesting the enclave. + /// Indicates if this is a retry from a failed call. internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) + byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength, bool isRetry) { throw new PlatformNotSupportedException(); } - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) + internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) { throw new PlatformNotSupportedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs index 0c72beac9d..b8a52b9e4b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs @@ -89,7 +89,7 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider #region protected methods // Helper method to get the enclave session from the cache if present - protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionParameters, bool shouldGenerateNonce, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionParameters, bool shouldGenerateNonce, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { customData = null; customDataLength = 0; @@ -107,7 +107,7 @@ protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionPa { sameThreadRetry = true; } - else + else if (!isRetry) { // We are explicitly not signalling the event here, as we want to hold the event till driver calls CreateEnclaveSession // If we signal the event now, then multiple thread end up calling GetAttestationParameters which triggers the attestation workflow. @@ -124,7 +124,7 @@ protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionPa // In case of multi-threaded application, first thread will set the event and all the subsequent threads will wait here either until the enclave // session is created or timeout happens. - if (sessionCacheLockTaken || sameThreadRetry) + if (sessionCacheLockTaken || sameThreadRetry || isRetry) { // While the current thread is waiting for event to be signaled and in the meanwhile we already completed the attestation on different thread // then we need to signal the event here diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/NoneAttestationEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/NoneAttestationEnclaveProvider.cs index 84aea73940..fabd69c976 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/NoneAttestationEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/NoneAttestationEnclaveProvider.cs @@ -18,9 +18,9 @@ internal class NoneAttestationEnclaveProvider : EnclaveProviderBase // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(enclaveSessionParameters, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); + GetEnclaveSessionHelper(enclaveSessionParameters, false, isRetry, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. 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..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"; @@ -43,6 +45,11 @@ public static SqlConnectionEncryptOption Parse(string value) } } + internal static SqlConnectionEncryptOption Parse(bool value) + { + return value ? Mandatory : Optional; + } + /// public static bool TryParse(string value, out SqlConnectionEncryptOption result) { 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/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 10e5fb6697..c0f16fde3d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -703,6 +703,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _attestationProtocol = connectionOptions._attestationProtocol; _serverSPN = connectionOptions._serverSPN; _failoverPartnerSPN = connectionOptions._failoverPartnerSPN; + _hostNameInCertificate = connectionOptions._hostNameInCertificate; #if NETFRAMEWORK _connectionReset = connectionOptions._connectionReset; _contextConnection = connectionOptions._contextConnection; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index c4b9987f01..1f2e758cef 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -86,9 +86,9 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, bool isRetry, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { - GetEnclaveSessionHelper(enclaveSessionParameters, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); + GetEnclaveSessionHelper(enclaveSessionParameters, false, isRetry, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. @@ -161,8 +161,8 @@ private void VerifyAttestationInfo(string attestationUrl, HealthReport healthRep X509Certificate2Collection signingCerts = GetSigningCertificate(attestationUrl, shouldForceUpdateSigningKeys); // Verify SQL Health report root chain of trust is the HGS root signing cert - X509ChainStatusFlags chainStatus = VerifyHealthReportAgainstRootCertificate(signingCerts, healthReport.Certificate); - if (chainStatus != X509ChainStatusFlags.NoError) + if (!VerifyHealthReportAgainstRootCertificate(signingCerts, healthReport.Certificate, out X509ChainStatusFlags chainStatus) || + chainStatus != X509ChainStatusFlags.NoError) { // In cases if we fail to validate the health report, it might be possible that we are using old signing keys // let's re-download the signing keys again and re-validate the health report @@ -223,19 +223,34 @@ private bool AnyCertificatesExpired(X509Certificate2Collection certificates) return certificates.OfType().Any(c => c.NotAfter < DateTime.Now); } - // Verifies that a chain of trust can be built from the health report provided - // by SQL Server and the attestation service's root signing certificate(s). - private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certificate2Collection signingCerts, X509Certificate2 healthReportCert) + /// + /// Verifies that a chain of trust can be built from the health report provided + /// by SQL Server and the attestation service's root signing certificate(s). + /// + /// If the method returns false, the value of chainStatus doesn't matter. The chain could not be validated. + /// + /// + /// + /// + /// A that indicates if the certificate was able to be verified. + private bool VerifyHealthReportAgainstRootCertificate(X509Certificate2Collection signingCerts, X509Certificate2 healthReportCert, out X509ChainStatusFlags chainStatus) { var chain = new X509Chain(); + chainStatus = X509ChainStatusFlags.NoError; foreach (var cert in signingCerts) { chain.ChainPolicy.ExtraStore.Add(cert); } + // An Always Encrypted-enabled driver doesn't verify an expiration date or a certificate authority chain. + // A certificate is simply used as a key pair consisting of a public and private key. This is by design. + + #pragma warning disable IA5352 + // CodeQL [SM00395] By design. Always Encrypted certificates should not be checked. chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - + #pragma warning restore IA5352 + if (!chain.Build(healthReportCert)) { bool untrustedRoot = false; @@ -249,9 +264,14 @@ private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certif } else { - return status.Status; + chainStatus = status.Status; + return true; } } + // The only ways past or out of the loop are: + // 1. untrustedRoot is true, in which case we want to continue to below + // 2. chainStatus is set to the first status in the chain and we return true + // 3. the ChainStatus is empty // if the chain failed with untrusted root, this could be because the client doesn't have the root cert // installed. If the chain's untrusted root cert has the same thumbprint as the signing cert, then we @@ -268,17 +288,21 @@ private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certif { if (element.Certificate.Thumbprint == cert.Thumbprint) { - return X509ChainStatusFlags.NoError; + return true; } } } // in the case where we didn't find matching thumbprint - return X509ChainStatusFlags.UntrustedRoot; + chainStatus = X509ChainStatusFlags.UntrustedRoot; + return true; } + + // There was an unknown failure and X509Chain.Build() returned an empty ChainStatus. + return false; } - return X509ChainStatusFlags.NoError; + return true; } // Verifies the enclave report signature using the health report. 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..fcc368dc8a 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/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 039bf2f766..e3183ee6a0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -7,6 +7,7 @@ using System.Data.Common; using System.Reflection; using System.Security; +using System.Threading.Tasks; using Microsoft.SqlServer.TDS.Servers; using Xunit; @@ -34,6 +35,25 @@ public void IntegratedAuthConnectionTest() connection.Open(); } + /// + /// Runs a test where TDS Server doesn't send encryption info during pre-login response. + /// The driver is expected to fail when that happens, and terminate the connection during pre-login phase + /// when client enables encryption using Encrypt=true or uses default encryption setting. + /// + [Fact] + public async Task PreLoginEncryptionExcludedTest() + { + using TestTdsServer server = TestTdsServer.StartTestServer(false, false, 5, excludeEncryption: true); + SqlConnectionStringBuilder builder = new(server.ConnectionString) + { + IntegratedSecurity = true + }; + + using SqlConnection connection = new(builder.ConnectionString); + Exception ex = await Assert.ThrowsAsync(async () => await connection.OpenAsync()); + Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase); + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArmProcess))] [InlineData(40613)] [InlineData(42108)] diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 0825760e45..557833e80c 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 @@ -253,7 +260,7 @@ public void SetInvalidPacketSize_Throws(int invalid) } [Theory] - [InlineData("AttachDBFilename","somefile.db")] + [InlineData("AttachDBFilename", "somefile.db")] public void SetKeyword(string keyword, string value) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); @@ -380,7 +387,7 @@ public void ConnectionBuilderEncryptBackwardsCompatibility() builder.Encrypt = false; Assert.Equal("Encrypt=False", builder.ConnectionString); Assert.False(builder.Encrypt); - + builder.Encrypt = true; Assert.Equal("Encrypt=True", builder.ConnectionString); Assert.True(builder.Encrypt); @@ -402,6 +409,18 @@ public void ConnectionBuilderEncryptBackwardsCompatibility() Assert.True(builder.Encrypt); } + [Fact] + public void EncryptParserValidValuesPropertyIndexerForEncryptionOption() + { + SqlConnectionStringBuilder builder = new(); + builder["Encrypt"] = SqlConnectionEncryptOption.Strict; + CheckEncryptType(builder, SqlConnectionEncryptOption.Strict); + builder["Encrypt"] = SqlConnectionEncryptOption.Optional; + CheckEncryptType(builder, SqlConnectionEncryptOption.Optional); + builder["Encrypt"] = SqlConnectionEncryptOption.Mandatory; + CheckEncryptType(builder, SqlConnectionEncryptOption.Mandatory); + } + [Theory] [InlineData("true", "True")] [InlineData("mandatory", "True")] @@ -413,6 +432,16 @@ public void ConnectionBuilderEncryptBackwardsCompatibility() public void EncryptParserValidValuesParsesSuccessfully(string value, string expectedValue) => Assert.Equal(expectedValue, SqlConnectionEncryptOption.Parse(value).ToString()); + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EncryptParserValidValuesPropertyIndexerForBoolean(bool value) + { + SqlConnectionStringBuilder builder = new(); + builder["Encrypt"] = value; + CheckEncryptType(builder, value ? SqlConnectionEncryptOption.Mandatory : SqlConnectionEncryptOption.Optional); + } + [Theory] [InlineData("something")] [InlineData("")] @@ -446,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); @@ -467,5 +649,11 @@ internal void ExecuteConnectionStringTests(string connectionString) Assert.NotNull(connection); } } + + internal static void CheckEncryptType(SqlConnectionStringBuilder builder, SqlConnectionEncryptOption expectedValue) + { + Assert.IsType(builder.Encrypt); + Assert.Equal(expectedValue, builder.Encrypt); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index 13fc5c22de..669e5b611d 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -945,6 +945,15 @@ public void Open_ConnectionString_Whitespace() Assert.NotNull(ex.Message); } + [Fact] + public void Open_ConnectionString_UserInstance() + { + SqlConnection cn = new SqlConnection("User Instance=true;"); + SqlException ex = Assert.Throws(() => cn.Open()); + // Throws without access violation + Assert.NotNull(ex.Message); + } + [Fact] public void ServerVersion_Connection_Closed() { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs index 51e2330bf0..1ead74f58d 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs @@ -25,7 +25,7 @@ public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args) Engine = engine; } - public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "") + public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "") { TDSServerArguments args = new TDSServerArguments() { @@ -36,6 +36,10 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool { args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired; } + if (excludeEncryption) + { + args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None; + } TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args); server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; @@ -45,14 +49,17 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; - server._connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional }; + server._connectionStringBuilder = excludeEncryption + // Allow encryption to be set when encryption is to be excluded from pre-login response. + ? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory } + : new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional }; server.ConnectionString = server._connectionStringBuilder.ConnectionString; return server; } - public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "") + public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "") { - return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, methodName); + return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, excludeEncryption, methodName); } public void Dispose() => _endpoint?.Stop(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs index a9d69cd6e2..893c50799b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs @@ -3,12 +3,10 @@ // See the LICENSE file in the project root for more information. using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; -using Azure.Identity; using Xunit; using Azure.Security.KeyVault.Keys; using System.Reflection; using System; -using System.Linq; using System.Collections.Generic; using System.Threading; using System.Diagnostics.Tracing; @@ -86,8 +84,7 @@ public static void TokenCredentialTest() Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); - ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential); + SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek); @@ -104,8 +101,7 @@ public static void TokenCredentialRotationTest() // SqlClientCustomTokenCredential implements a legacy authentication callback to request the access token from the client-side. SqlColumnEncryptionAzureKeyVaultProvider oldAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential()); - ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential); + SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithNewProvider); @@ -129,15 +125,14 @@ public static void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion() { string keyName = keyPathUri.Segments[2]; string keyVersion = keyPathUri.Segments[3]; - ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - KeyClient keyClient = new KeyClient(vaultUri, clientSecretCredential); + KeyClient keyClient = new KeyClient(vaultUri, DataTestUtility.GetTokenCredential()); KeyVaultKey currentVersionKey = keyClient.GetKey(keyName); KeyVaultKey specifiedVersionKey = keyClient.GetKey(keyName, keyVersion); //If specified versioned key is the most recent version of the key then we cannot test. if (!KeyIsLatestVersion(specifiedVersionKey, currentVersionKey)) { - SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential); + SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); // Perform an operation to initialize the internal caches azureKeyProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVOriginalUrl, EncryptionAlgorithm, s_columnEncryptionKey); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 5f60faddb5..8e7458f02e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; @@ -56,7 +57,7 @@ public ApiShould(PlatformSpecificTestContext context) DummyKeyStoreProvider.Name, _lastTenBytesCek); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithBooleanVariable))] public void TestSqlTransactionCommitRollbackWithTransparentInsert(string connection, bool isCommitted) { @@ -93,7 +94,7 @@ public void TestSqlTransactionCommitRollbackWithTransparentInsert(string connect } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestSqlTransactionRollbackToSavePoint(string connection) { @@ -139,7 +140,7 @@ public void TestSqlTransactionRollbackToSavePoint(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void SqlParameterProperties(string connection) { @@ -350,7 +351,7 @@ public void SqlParameterProperties(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestSqlDataAdapterFillDataTable(string connection) { @@ -423,7 +424,7 @@ public void TestSqlDataAdapterFillDataTable(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithSchemaType))] public void TestSqlDataAdapterFillSchema(string connection, SchemaType schemaType) { @@ -470,7 +471,7 @@ private void ValidateSchema(DataColumnCollection dataColumns) Assert.Equal(typeof(string), dataColumns[2].DataType); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithBooleanVariable))] public void TestExecuteNonQuery(string connection, bool isAsync) { @@ -538,7 +539,7 @@ public void TestExecuteNonQuery(string connection, bool isAsync) }); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithBooleanVariable))] public void TestExecuteScalar(string connection, bool isAsync) { @@ -590,7 +591,7 @@ public void TestExecuteScalar(string connection, bool isAsync) }); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithIntegers))] public void TestSqlDataAdapterBatchUpdate(string connection, int numberofRows) { @@ -635,7 +636,7 @@ public void TestSqlDataAdapterBatchUpdate(string connection, int numberofRows) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestExecuteReader(string connection) { @@ -689,7 +690,60 @@ public void TestExecuteReader(string connection) }); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] + [ClassData(typeof(AEConnectionStringProvider))] + public async void TestExecuteReaderAsyncWithLargeQuery(string connectionString) + { + string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false); + if (randomName.Length > 50) + { + randomName = randomName.Substring(0, 50); + } + string tableName = $"VeryLong_{randomName}_TestTableName"; + int columnsCount = 50; + + // Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49) + try + { + CreateTable(connectionString, tableName, columnsCount); + string name = "nobody"; + + using (SqlConnection connection = new SqlConnection(connectionString)) + { + await connection.OpenAsync(); + // This creates a "select top 100" query that has over 40k characters + using (SqlCommand sqlCommand = new SqlCommand(GenerateSelectQuery(tableName, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), + connection, + transaction: null, + columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) + { + sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); + sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); + + sqlCommand.Parameters[0].Value = 0; + sqlCommand.Parameters[1].Value = name; + + // Act and Assert + // Test that execute reader async does not throw an exception. + // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. + using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync()) + { + Assert.False(sqlDataReader.HasRows, "The table should be empty"); + } + } + } + } + catch + { + throw; + } + finally + { + DropTableIfExists(connectionString, tableName); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet1))] public void TestExecuteReaderWithCommandBehavior(string connection, CommandBehavior commandBehavior) { @@ -814,7 +868,7 @@ public void TestExecuteReaderWithCommandBehavior(string connection, CommandBehav }); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectionString) { @@ -861,7 +915,7 @@ public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectio } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestPrepareWithExecuteNonQuery(string connection) { @@ -910,7 +964,7 @@ public void TestPrepareWithExecuteNonQuery(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestAsyncWriteDelayWithExecuteNonQueryAsync(string connection) { @@ -964,7 +1018,7 @@ public void TestAsyncWriteDelayWithExecuteNonQueryAsync(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestAsyncWriteDelayWithExecuteReaderAsync(string connection) { @@ -1031,7 +1085,7 @@ public void TestAsyncWriteDelayWithExecuteReaderAsync(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestPrepareWithExecuteNonQueryAsync(string connection) { @@ -1086,7 +1140,7 @@ public void TestPrepareWithExecuteNonQueryAsync(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet2))] public void TestPrepareWithExecuteReaderAsync(string connection, CommandBehavior commandBehavior) { @@ -1148,7 +1202,7 @@ public void TestPrepareWithExecuteReaderAsync(string connection, CommandBehavior } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestSqlDataReaderAPIs(string connection) { @@ -1345,7 +1399,7 @@ public void TestSqlDataReaderAPIs(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestSqlDataReaderAPIsWithSequentialAccess(string connection) { @@ -1773,7 +1827,7 @@ public void TestSqlDataReaderAPIsWithSequentialAccess(string connection) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet2))] public void TestSqlCommandSequentialAccessCodePaths(string connection, CommandBehavior value) { @@ -1817,7 +1871,7 @@ public void TestSqlCommandSequentialAccessCodePaths(string connection, CommandBe } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestExecuteXmlReader(string connection) { @@ -2072,7 +2126,7 @@ public void TestSqlCommandCancel(string connection, string value, int number) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithCancellationTime))] public void TestSqlCommandCancellationToken(string connection, int initalValue, int cancellationTime) { @@ -2140,7 +2194,7 @@ public void TestNoneAttestationProtocolWithSGXEnclave() } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestConnectionCustomKeyStoreProviderDuringAeQuery(string connectionString) { @@ -2195,7 +2249,7 @@ public void TestConnectionCustomKeyStoreProviderDuringAeQuery(string connectionS } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] [ClassData(typeof(AEConnectionStringProvider))] public void TestCommandCustomKeyStoreProviderDuringAeQuery(string connectionString) { @@ -2246,7 +2300,7 @@ public void TestCommandCustomKeyStoreProviderDuringAeQuery(string connectionStri // On Windows, "_fixture" will be type SQLSetupStrategyCertStoreProvider // On non-Windows, "_fixture" will be type SQLSetupStrategyAzureKeyVault // Test will pass on both but only SQLSetupStrategyCertStoreProvider is a system provider - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestSystemProvidersHavePrecedenceOverInstanceLevelProviders(string connectionString) { @@ -2336,6 +2390,92 @@ public void TestRetryWhenAEParameterMetadataCacheIsStale(string connectionString cmd.ExecuteNonQuery(); } + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.EnclaveEnabled))] + [ClassData(typeof(AEConnectionStringProvider))] + public void TestRetryWhenAEEnclaveCacheIsStale(string connectionString) + { + CleanUpTable(connectionString, _tableName); + + const int customerId = 50; + IList values = GetValues(dataHint: customerId); + InsertRows(tableName: _tableName, numberofRows: 1, values: values, connection: connectionString); + + ApiTestTable table = _fixture.ApiTestTable as ApiTestTable; + string enclaveSelectQuery = $@"SELECT CustomerId, FirstName, LastName FROM [{_tableName}] WHERE CustomerId > @CustomerId"; + string alterCekQueryFormatString = "ALTER TABLE [{0}] " + + "ALTER COLUMN [CustomerId] [int] " + + "ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{1}], " + + "ENCRYPTION_TYPE = Randomized, " + + "ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'); " + + "ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;"; + + using SqlConnection sqlConnection = new(connectionString); + sqlConnection.Open(); + + // change the CEK and encryption type to randomized for the CustomerId column to ensure enclaves are used + using SqlCommand cmd = new SqlCommand( + string.Format(alterCekQueryFormatString, _tableName, table.columnEncryptionKey2.Name), + sqlConnection, + null, + SqlCommandColumnEncryptionSetting.Enabled); + cmd.ExecuteNonQuery(); + + // execute the select query to create the cache entry + cmd.CommandText = enclaveSelectQuery; + cmd.Parameters.AddWithValue("@CustomerId", 0); + using (SqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + Assert.Equal(customerId, (int)reader[0]); + } + reader.Close(); + } + + CommandHelper.InvalidateEnclaveSession(cmd); + + // Execute again to exercise the session retry logic + using (SqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + Assert.Equal(customerId, (int)reader[0]); + } + reader.Close(); + } + + CommandHelper.InvalidateEnclaveSession(cmd); + + // Execute again to exercise the async session retry logic + Task readAsyncTask = ReadAsync(cmd, values, CommandBehavior.Default); + readAsyncTask.GetAwaiter().GetResult(); + +#if DEBUG + CommandHelper.ForceThrowDuringGenerateEnclavePackage(cmd); + + // Execute again to exercise the session retry logic + using (SqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + Assert.Equal(customerId, (int)reader[0]); + } + reader.Close(); + } + + CommandHelper.ForceThrowDuringGenerateEnclavePackage(cmd); + + // Execute again to exercise the async session retry logic + Task readAsyncTask2 = ReadAsync(cmd, values, CommandBehavior.Default); + readAsyncTask2.GetAwaiter().GetResult(); +#endif + + // revert the CEK change to the CustomerId column + cmd.Parameters.Clear(); + cmd.CommandText = string.Format(alterCekQueryFormatString, _tableName, table.columnEncryptionKey1.Name); + cmd.ExecuteNonQuery(); + } + private void ExecuteQueryThatRequiresCustomKeyStoreProvider(SqlConnection connection) { using (SqlCommand command = CreateCommandThatRequiresCustomKeyStoreProvider(connection)) @@ -2806,6 +2946,75 @@ private void CleanUpTable(string connString, string tableName) } } + private static void CreateTable(string connString, string tableName, int columnsCount) + => DataTestUtility.RunNonQuery(connString, GenerateCreateQuery(tableName, columnsCount)); + /// + /// Drops the table if the specified table exists + /// + /// The connection string to the database + /// The name of the table to be dropped + private static void DropTableIfExists(string connString, string tableName) + { + using var sqlConnection = new SqlConnection(connString); + sqlConnection.Open(); + DataTestUtility.DropTable(sqlConnection, tableName); + } + + /// + /// Generates the query for creating a table with the number of bit columns specified. + /// + /// The name of the table + /// The number of columns for the table + /// + private static string GenerateCreateQuery(string tableName, int columnsCount) + { + StringBuilder builder = new StringBuilder(); + builder.Append(string.Format("CREATE TABLE [dbo].[{0}]", tableName)); + builder.Append('('); + builder.AppendLine("[ID][bigint] NOT NULL,"); + builder.AppendLine("[Name] [varchar] (200) NOT NULL"); + for (int i = 0; i < columnsCount; i++) + { + builder.Append(','); + builder.Append($"[ColumnName{i}][bit] NULL"); + } + builder.Append(");"); + + return builder.ToString(); + } + + /// + /// Generates the large query with the select top 100 of all the columns repeated multiple times. + /// + /// The name of the table + /// The number of columns to be explicitly included + /// The number of times the select query is repeated + /// A where clause for additional filters + /// + private static string GenerateSelectQuery(string tableName, int columnsCount, int repeat = 10, string where = "") + { + StringBuilder builder = new StringBuilder(); + builder.AppendLine($"SELECT TOP 100"); + builder.AppendLine($"[{tableName}].[ID],"); + builder.AppendLine($"[{tableName}].[Name]"); + for (int i = 0; i < columnsCount; i++) + { + builder.Append(","); + builder.AppendLine($"[{tableName}].[ColumnName{i}]"); + } + + string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) [{tableName}]" : where; + builder.AppendLine($"FROM [{tableName}] {extra};"); + + StringBuilder builder2 = new StringBuilder(); + for (int i = 0; i < repeat; i++) + { + builder2.AppendLine(builder.ToString()); + } + + return builder2.ToString(); + } + /// /// An helper method to test the cancellation of the command using cancellationToken to async SqlCommand APIs. /// @@ -2960,6 +3169,10 @@ public Customer(int id, string firstName, string lastName) public string LastName { get; set; } } +#if NET6_0_OR_GREATER + public record CustomerDateOnly(int Id, string FirstName, string LastName, DateOnly DateOfBirth, TimeOnly TimeOfDay); +#endif + internal class TestAsyncCallBackStateObject { /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs index 6a58935cff..20cd452286 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs @@ -24,7 +24,7 @@ public BulkCopyAE(PlatformSpecificTestContext context) tableName = fixture.BulkCopyAETestTable.Name; } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TestBulkCopyString(string connectionString) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs index cf2da1c4b6..9d6080b24f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs @@ -25,7 +25,7 @@ public BulkCopyAEErrorMessage(PlatformSpecificTestContext context) _columnName = "c1"; } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void TextToIntErrorMessageTest(string connectionString) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.cs new file mode 100644 index 0000000000..a211fe6343 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.cs @@ -0,0 +1,93 @@ +// 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.Collections; +using System.Collections.Generic; +using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public sealed class DateOnlyReadTests : IClassFixture, IDisposable + { + private SQLSetupStrategy fixture; + + private readonly string tableName; + + public DateOnlyReadTests(PlatformSpecificTestContext context) + { + fixture = context.Fixture; + tableName = fixture.DateOnlyTestTable.Name; + } + + // tests + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] + [ClassData(typeof(TestSelectOnEncryptedNonEncryptedColumnsDataDateOnly))] + public void TestSelectOnEncryptedNonEncryptedColumns(string connString, string selectQuery, int totalColumnsInSelect, string[] types) + { + Assert.False(string.IsNullOrWhiteSpace(selectQuery), "FAILED: select query should not be null or empty."); + Assert.True(totalColumnsInSelect <= 3, "FAILED: totalColumnsInSelect should <= 3."); + + using (SqlConnection sqlConn = new SqlConnection(connString)) + { + sqlConn.Open(); + + Table.DeleteData(tableName, sqlConn); + + // insert 1 row data + CustomerDateOnly customer = new CustomerDateOnly( + 45, + "Microsoft", + "Corporation", + new DateOnly(2001, 1, 31), + new TimeOnly(18, 36, 45)); + + DatabaseHelper.InsertCustomerDateOnlyData(sqlConn, null, tableName, customer); + + using (SqlCommand sqlCommand = new SqlCommand(string.Format(selectQuery, tableName), + sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled)) + { + using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader()) + { + Assert.True(sqlDataReader.HasRows, "FAILED: Select statement did not return any rows."); + + while (sqlDataReader.Read()) + { + DatabaseHelper.CompareResults(sqlDataReader, types, totalColumnsInSelect); + } + } + } + } + } + + + public void Dispose() + { + foreach (string connStrAE in DataTestUtility.AEConnStringsSetup) + { + using (SqlConnection sqlConnection = new SqlConnection(connStrAE)) + { + sqlConnection.Open(); + Table.DeleteData(fixture.DateOnlyTestTable.Name, sqlConnection); + } + } + } + } + + public class TestSelectOnEncryptedNonEncryptedColumnsDataDateOnly : IEnumerable + { + public IEnumerator GetEnumerator() + { + foreach (string connStrAE in DataTestUtility.AEConnStrings) + { + yield return new object[] { connStrAE, @"select CustomerId, FirstName, LastName from [{0}] ", 3, new string[] { @"int", @"string", @"string" } }; + yield return new object[] { connStrAE, @"select CustomerId, FirstName from [{0}] ", 2, new string[] { @"int", @"string" } }; + yield return new object[] { connStrAE, @"select LastName from [{0}] ", 1, new string[] { @"string" }}; + yield return new object[] { connStrAE, @"select DateOfBirth, TimeOfDay from [{0}] ", 2, new string[] { @"DateOnly", "TimeOnly" } }; + } + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs index 4862a41d03..ea4b0171eb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs @@ -24,7 +24,7 @@ public End2EndSmokeTests(PlatformSpecificTestContext context) } // tests - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(TestSelectOnEncryptedNonEncryptedColumnsData))] public void TestSelectOnEncryptedNonEncryptedColumns(string connString, string selectQuery, int totalColumnsInSelect, string[] types) { @@ -58,7 +58,7 @@ public void TestSelectOnEncryptedNonEncryptedColumns(string connString, string s } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(TestSelectOnEncryptedNonEncryptedColumnsWithEncryptedParametersData))] public void TestSelectOnEncryptedNonEncryptedColumnsWithEncryptedParameters(string connString, bool sync, diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs index 32dd86cd94..e5c4429752 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs @@ -23,7 +23,7 @@ public SqlBulkCopyTruncation(PlatformSpecificTestContext context) tableNames = _fixture.sqlBulkTruncationTableNames; } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyTestsInt(string connectionString) { @@ -40,7 +40,7 @@ public void BulkCopyTestsInt(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void DirectInsertTest1(string connectionString) { @@ -80,7 +80,7 @@ public void DirectInsertTest1(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void DirectInsertTest2(string connectionString) { @@ -127,7 +127,7 @@ public void DirectInsertTest2(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void DirectInsertTest3(string connectionString) { @@ -172,7 +172,7 @@ public void DirectInsertTest3(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void DirectInsertTest4(string connectionString) { @@ -209,7 +209,7 @@ public void DirectInsertTest4(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyDatetime2Tests(string connectionString) { @@ -258,7 +258,7 @@ public void BulkCopyDatetime2Tests(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyDecimal(string connectionString) { @@ -275,7 +275,7 @@ public void BulkCopyDecimal(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyVarchar(string connectionString) { @@ -304,7 +304,7 @@ public void BulkCopyVarchar(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyVarcharMax(string connectionString) { @@ -333,7 +333,7 @@ public void BulkCopyVarcharMax(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyNVarchar(string connectionString) { @@ -350,7 +350,7 @@ public void BulkCopyNVarchar(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyNVarcharMax(string connectionString) { @@ -367,7 +367,7 @@ public void BulkCopyNVarcharMax(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopyBinaryMax(string connectionString) { @@ -407,7 +407,7 @@ public void BulkCopyBinaryMax(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopySmallBinary(string connectionString) { @@ -476,7 +476,7 @@ public void BulkCopySmallBinary(string connectionString) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProvider))] public void BulkCopySmallChar(string connectionString) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs index 2f1a1d8ed6..ed3d9797dd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs @@ -56,7 +56,7 @@ public SqlNullValuesTests(PlatformSpecificTestContext context) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(NullValueTestsData))] public void NullValueTests(string connString, ConnStringColumnEncryptionSetting connStringSetting, SqlCommandColumnEncryptionSetting commandSetting, ReturnValueSetting nullReturnValue) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs index 9cea6b8239..86fa2c38c5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs @@ -31,6 +31,27 @@ internal static void InsertCustomerData(SqlConnection sqlConnection, SqlTransact sqlCommand.ExecuteNonQuery(); } +#if NET6_0_OR_GREATER + /// + /// Insert CustomerDateOnly record into table + /// + internal static void InsertCustomerDateOnlyData(SqlConnection sqlConnection, SqlTransaction transaction, string tableName, CustomerDateOnly customer) + { + using SqlCommand sqlCommand = new( + $"INSERT INTO [{tableName}] (CustomerId, FirstName, LastName, DateOfBirth, TimeOfDay) VALUES (@CustomerId, @FirstName, @LastName, @DateOfBirth, @TimeOfDay);", + connection: sqlConnection, + transaction: transaction, + columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled); + + sqlCommand.Parameters.AddWithValue(@"CustomerId", customer.Id); + sqlCommand.Parameters.AddWithValue(@"FirstName", customer.FirstName); + sqlCommand.Parameters.AddWithValue(@"LastName", customer.LastName); + sqlCommand.Parameters.AddWithValue(@"DateOfBirth", customer.DateOfBirth); + sqlCommand.Parameters.AddWithValue(@"TimeOfDay", customer.TimeOfDay); + sqlCommand.ExecuteNonQuery(); + } +#endif + /// /// Validates that the results are the ones expected. /// @@ -155,7 +176,15 @@ public static void CompareResults(SqlDataReader sqlDataReader, string[] paramete case "int": Assert.True(sqlDataReader.GetInt32(columnsRead) == 45, "FAILED: read int value does not match."); break; +#if NET6_0_OR_GREATER + case "DateOnly": + Assert.True(sqlDataReader.GetFieldValue(columnsRead) == new DateOnly(2001, 1, 31), "FAILED: read DateOnly value does not match."); + break; + case "TimeOnly": + Assert.True(sqlDataReader.GetFieldValue(columnsRead) == new TimeOnly(18, 36, 45), "FAILED: read TimeOnly value does not match."); + break; +#endif default: Assert.True(false, "FAILED: unexpected data type."); break; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 0cd8436cb4..4d3c635684 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -1,4 +1,5 @@ -// Licensed to the .NET Foundation under one or more agreements. + +// 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; @@ -21,6 +22,7 @@ public class SQLSetupStrategy : IDisposable public Table BulkCopyAEErrorMessageTestTable { get; private set; } public Table BulkCopyAETestTable { get; private set; } public Table SqlParameterPropertiesTable { get; private set; } + public Table DateOnlyTestTable { get; private set; } public Table End2EndSmokeTable { get; private set; } public Table TrustedMasterKeyPathsTestTable { get; private set; } public Table SqlNullValuesTable { get; private set; } @@ -131,6 +133,9 @@ protected List CreateTables(IList columnEncryptionKe End2EndSmokeTable = new ApiTestTable(GenerateUniqueName("End2EndSmokeTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]); tables.Add(End2EndSmokeTable); + DateOnlyTestTable = new DateOnlyTestTable(GenerateUniqueName("DateOnlyTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]); + tables.Add(DateOnlyTestTable); + TrustedMasterKeyPathsTestTable = new ApiTestTable(GenerateUniqueName("TrustedMasterKeyPathsTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]); tables.Add(TrustedMasterKeyPathsTestTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index 1c054f3769..3d3c717b31 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs @@ -141,8 +141,7 @@ internal static X509Certificate2 CreateCertificate() private static async Task SetupAKVKeysAsync() { - ClientSecretCredential clientSecretCredential = new ClientSecretCredential(DataTestUtility.AKVTenantId, DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, clientSecretCredential); + KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()); AsyncPageable keys = keyClient.GetPropertiesOfKeysAsync(); IAsyncEnumerator enumerator = keys.GetAsyncEnumerator(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/DateOnlyTestTable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/DateOnlyTestTable.cs new file mode 100644 index 0000000000..6cc9bb6f66 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/DateOnlyTestTable.cs @@ -0,0 +1,42 @@ +// 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. + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup +{ + public class DateOnlyTestTable : Table + { + private const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256"; + public ColumnEncryptionKey columnEncryptionKey1; + public ColumnEncryptionKey columnEncryptionKey2; + private bool useDeterministicEncryption; + + public DateOnlyTestTable(string tableName, ColumnEncryptionKey columnEncryptionKey1, ColumnEncryptionKey columnEncryptionKey2, bool useDeterministicEncryption = false) : base(tableName) + { + this.columnEncryptionKey1 = columnEncryptionKey1; + this.columnEncryptionKey2 = columnEncryptionKey2; + this.useDeterministicEncryption = useDeterministicEncryption; + } + + public override void Create(SqlConnection sqlConnection) + { + string encryptionType = useDeterministicEncryption ? "DETERMINISTIC" : DataTestUtility.EnclaveEnabled ? "RANDOMIZED" : "DETERMINISTIC"; + string sql = + $@"CREATE TABLE [dbo].[{Name}] + ( + [CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = {encryptionType}, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), + [FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), + [LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey2.Name}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), + [TimeOfDay] [time] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), + [DateOfBirth] [date] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{columnEncryptionKey1.Name}], ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = '{ColumnEncryptionAlgorithmName}') + )"; + + using (SqlCommand command = sqlConnection.CreateCommand()) + { + command.CommandText = sql; + command.ExecuteNonQuery(); + } + } + + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/AADUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/AADUtility.cs index 6abff25d13..7111ecbbc8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/AADUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/AADUtility.cs @@ -7,25 +7,11 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Microsoft.IdentityModel.Clients.ActiveDirectory; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public static class AADUtility { - public static async Task AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope) - { - var authContext = new AuthenticationContext(authority); - ClientCredential clientCred = new ClientCredential(DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred); - if (result == null) - { - throw new Exception($"Failed to retrieve an access token for {resource}"); - } - - return result.AccessToken; - } - public static async Task GetManagedIdentityToken(string clientId = null) => await new MockManagedIdentityTokenProvider().AcquireTokenAsync(clientId).ConfigureAwait(false); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index b6a7cff18f..f0b930facf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -20,6 +20,9 @@ using Xunit; using System.Net.NetworkInformation; using System.Text; +using System.Security.Principal; +using Azure.Identity; +using Azure.Core; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -28,7 +31,6 @@ public static class DataTestUtility public static readonly string NPConnectionString = null; public static readonly string TCPConnectionString = null; public static readonly string TCPConnectionStringHGSVBS = null; - public static readonly string TCPConnectionStringAASVBS = null; public static readonly string TCPConnectionStringNoneVBS = null; public static readonly string TCPConnectionStringAASSGX = null; public static readonly string AADAuthorityURL = null; @@ -39,8 +41,6 @@ public static class DataTestUtility public static readonly string AKVUrl = null; public static readonly string AKVOriginalUrl = null; public static readonly string AKVTenantId = null; - public static readonly string AKVClientId = null; - public static readonly string AKVClientSecret = null; public static readonly string LocalDbAppName = null; public static readonly string LocalDbSharedInstanceName = null; public static List AEConnStrings = new List(); @@ -61,10 +61,10 @@ public static class DataTestUtility public static readonly bool IsDNSCachingSupportedTR = false; // this is for the tenant ring public static readonly string UserManagedIdentityClientId = null; - public static readonly string EnclaveAzureDatabaseConnString = null; public static bool ManagedIdentitySupported = true; public static string AADAccessToken = null; + public static bool SupportsSystemAssignedManagedIdentity = false; public static string AADSystemIdentityAccessToken = null; public static string AADUserIdentityAccessToken = null; public const string ApplicationClientId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; @@ -84,13 +84,21 @@ public static class DataTestUtility public static readonly string KerberosDomainUser = null; internal static readonly string KerberosDomainPassword = null; + public static bool TcpConnectionStringDoesNotUseAadAuth + { + get + { + SqlConnectionStringBuilder builder = new (TCPConnectionString); + return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified; + } + } + static DataTestUtility() { Config c = Config.Load(); NPConnectionString = c.NPConnectionString; TCPConnectionString = c.TCPConnectionString; TCPConnectionStringHGSVBS = c.TCPConnectionStringHGSVBS; - TCPConnectionStringAASVBS = c.TCPConnectionStringAASVBS; TCPConnectionStringNoneVBS = c.TCPConnectionStringNoneVBS; TCPConnectionStringAASSGX = c.TCPConnectionStringAASSGX; AADAuthorityURL = c.AADAuthorityURL; @@ -140,8 +148,6 @@ static DataTestUtility() } AKVTenantId = c.AzureKeyVaultTenantId; - AKVClientId = c.AzureKeyVaultClientId; - AKVClientSecret = c.AzureKeyVaultClientSecret; if (EnclaveEnabled) { @@ -151,11 +157,6 @@ static DataTestUtility() AEConnStringsSetup.Add(TCPConnectionStringHGSVBS); } - if (!string.IsNullOrEmpty(TCPConnectionStringAASVBS)) - { - AEConnStrings.Add(TCPConnectionStringAASVBS); - } - if (!string.IsNullOrEmpty(TCPConnectionStringNoneVBS)) { AEConnStrings.Add(TCPConnectionStringNoneVBS); @@ -338,15 +339,36 @@ public static bool IsNotAzureServer() // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse public static bool IsAKVSetupAvailable() { - return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(AKVClientId) && !string.IsNullOrEmpty(AKVClientSecret) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); + return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); + } + + private static readonly DefaultAzureCredential s_defaultCredential = new(new DefaultAzureCredentialOptions { ManagedIdentityClientId = UserManagedIdentityClientId }); + + public static TokenCredential GetTokenCredential() + { + return s_defaultCredential; + } + + public static bool IsTargetReadyForAeWithKeyStore() + { + return DataTestUtility.AreConnStringSetupForAE() +#if NET6_0_OR_GREATER + // AE tests on Windows will use the Cert Store. On non-Windows, they require AKV. + && (OperatingSystem.IsWindows() || DataTestUtility.IsAKVSetupAvailable()) +#endif + ; } public static bool IsUsingManagedSNI() => UseManagedSNIOnWindows; public static bool IsNotUsingManagedSNIOnWindows() => !UseManagedSNIOnWindows; - public static bool IsUsingNativeSNI() => !IsUsingManagedSNI(); - + public static bool IsUsingNativeSNI() => +#if !NETFRAMEWORK + DataTestUtility.IsNotUsingManagedSNIOnWindows(); +#else + true; +#endif // Synapse: UTF8 collations are not supported with Azure Synapse. // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/40103791-utf-8-collations-should-be-supported-in-azure-syna public static bool IsUTF8Supported() @@ -437,6 +459,28 @@ public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = return name; } + public static bool IsSupportingDistributedTransactions() + { +#if NET7_0_OR_GREATER + return OperatingSystem.IsWindows() && System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture != System.Runtime.InteropServices.Architecture.X86 && IsNotAzureServer(); +#elif NETFRAMEWORK + return IsNotAzureServer(); +#else + return false; +#endif + } + + public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody) + { + DropTable(sqlConnection, tableName); + string tableCreate = "CREATE TABLE " + tableName + createBody; + using (SqlCommand command = sqlConnection.CreateCommand()) + { + command.CommandText = tableCreate; + command.ExecuteNonQuery(); + } + } + public static void DropTable(SqlConnection sqlConnection, string tableName) { ResurrectConnection(sqlConnection); @@ -506,7 +550,7 @@ public static string GetAccessToken() public static string GetSystemIdentityAccessToken() { - if (true == ManagedIdentitySupported && null == AADSystemIdentityAccessToken && IsAADPasswordConnStrSetup()) + if (ManagedIdentitySupported && SupportsSystemAssignedManagedIdentity && null == AADSystemIdentityAccessToken && IsAADPasswordConnStrSetup()) { AADSystemIdentityAccessToken = AADUtility.GetManagedIdentityToken().GetAwaiter().GetResult(); if (AADSystemIdentityAccessToken == null) @@ -519,7 +563,7 @@ public static string GetSystemIdentityAccessToken() public static string GetUserIdentityAccessToken() { - if (true == ManagedIdentitySupported && null == AADUserIdentityAccessToken && IsAADPasswordConnStrSetup()) + if (ManagedIdentitySupported && null == AADUserIdentityAccessToken && IsAADPasswordConnStrSetup()) { // Pass User Assigned Managed Identity Client Id here. AADUserIdentityAccessToken = AADUtility.GetManagedIdentityToken(UserManagedIdentityClientId).GetAwaiter().GetResult(); @@ -931,15 +975,24 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) /// Resolves the machine's fully qualified domain name if it is applicable. /// /// Returns FQDN if the client was domain joined otherwise the machine name. - public static string GetMachineFQDN() + public static string GetMachineFQDN(string hostname) { IPGlobalProperties machineInfo = IPGlobalProperties.GetIPGlobalProperties(); StringBuilder fqdn = new(); - fqdn.Append(machineInfo.HostName); - if (!string.IsNullOrEmpty(machineInfo.DomainName)) + if (hostname.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + hostname.Equals(machineInfo.HostName, StringComparison.OrdinalIgnoreCase)) + { + fqdn.Append(machineInfo.HostName); + if (!string.IsNullOrEmpty(machineInfo.DomainName)) + { + fqdn.Append("."); + fqdn.Append(machineInfo.DomainName); + } + } + else { - fqdn.Append("."); - fqdn.Append(machineInfo.DomainName); + IPHostEntry host = Dns.GetHostEntry(hostname); + fqdn.Append(host.HostName); } return fqdn.ToString(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index 23ed76f81e..977bc53257 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -3,20 +3,20 @@ // See the LICENSE file in the project root for more information. using System; -using System.IdentityModel.Tokens.Jwt; +using System.Collections.Concurrent; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Microsoft.IdentityModel.Clients.ActiveDirectory; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Azure.Identity; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class SqlClientCustomTokenCredential : TokenCredential { + private const string DEFAULT_PREFIX = "/.default"; + string _authority = ""; string _resource = ""; string _akvUrl = ""; @@ -70,40 +70,8 @@ private async Task AcquireTokenAsync() _akvUrl = DataTestUtility.AKVUrl; } - string strAccessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); - DateTime expiryTime = InterceptAccessTokenForExpiry(strAccessToken); - return new AccessToken(strAccessToken, new DateTimeOffset(expiryTime)); - } - - private DateTime InterceptAccessTokenForExpiry(string accessToken) - { - if (null == accessToken) - { - throw new ArgumentNullException(accessToken); - } - - var jwtHandler = new JwtSecurityTokenHandler(); - var jwtOutput = string.Empty; - - // Check Token Format - if (!jwtHandler.CanReadToken(accessToken)) - throw new FormatException(accessToken); - - JwtSecurityToken token = jwtHandler.ReadJwtToken(accessToken); - - // Re-serialize the Token Headers to just Key and Values - var jwtHeader = JsonConvert.SerializeObject(token.Header.Select(h => new { h.Key, h.Value })); - jwtOutput = $"{{\r\n\"Header\":\r\n{JToken.Parse(jwtHeader)},"; - - // Re-serialize the Token Claims to just Type and Values - var jwtPayload = JsonConvert.SerializeObject(token.Claims.Select(c => new { c.Type, c.Value })); - jwtOutput += $"\r\n\"Payload\":\r\n{JToken.Parse(jwtPayload)}\r\n}}"; - - // Output the whole thing to pretty JSON object formatted. - string jToken = JToken.Parse(jwtOutput).ToString(Formatting.Indented); - JToken payload = JObject.Parse(jToken).GetValue("Payload"); - - return new DateTime(1970, 1, 1).AddSeconds((long)payload[4]["Value"]); + AccessToken accessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); + return accessToken; } private static string ValidateChallenge(string challenge) @@ -127,16 +95,18 @@ private static string ValidateChallenge(string challenge) /// Authorization URL /// Resource /// - public static async Task AzureActiveDirectoryAuthenticationCallback(string authority, string resource) + public static async Task AzureActiveDirectoryAuthenticationCallback(string authority, string resource) { - var authContext = new AuthenticationContext(authority); - ClientCredential clientCred = new ClientCredential(DataTestUtility.AKVClientId, DataTestUtility.AKVClientSecret); - AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred); - if (result == null) - { - throw new InvalidOperationException($"Failed to retrieve an access token for {resource}"); - } - return result.AccessToken; + using CancellationTokenSource cts = new(); + cts.CancelAfter(30000); // Hard coded for tests + string[] scopes = new string[] { resource + DEFAULT_PREFIX }; + TokenRequestContext tokenRequestContext = new(scopes); + int separatorIndex = authority.LastIndexOf('/'); + string authorityHost = authority.Remove(separatorIndex + 1); + string audience = authority.Substring(separatorIndex + 1); + TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions() { AuthorityHost = new Uri(authorityHost) }; + AccessToken accessToken = await DataTestUtility.GetTokenCredential().GetTokenAsync(tokenRequestContext, cts.Token).ConfigureAwait(false); + return accessToken; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 5172452626..011fc0c3cf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -57,6 +57,7 @@ + @@ -70,6 +71,9 @@ + + + @@ -275,6 +279,7 @@ + @@ -310,18 +315,14 @@ - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs index d00a9cf8b1..9d43567cf1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs @@ -991,7 +991,14 @@ public void UpdateOffsetTest() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static bool CanRunSchemaTests() + { + return DataTestUtility.AreConnStringsSetup() && + // Tests switch to master database, which is not guaranteed when using AAD auth + DataTestUtility.TcpConnectionStringDoesNotUseAadAuth; + } + + [ConditionalFact(nameof(CanRunSchemaTests))] public void SelectAllTest() { // Test exceptions diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/CommandHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/CommandHelper.cs index 6d56c30e49..ba3d578673 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/CommandHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/CommandHelper.cs @@ -12,6 +12,11 @@ internal static class CommandHelper private static Type s_sqlCommand = typeof(SqlCommand); private static MethodInfo s_completePendingReadWithSuccess = s_sqlCommand.GetMethod("CompletePendingReadWithSuccess", BindingFlags.NonPublic | BindingFlags.Instance); private static MethodInfo s_completePendingReadWithFailure = s_sqlCommand.GetMethod("CompletePendingReadWithFailure", BindingFlags.NonPublic | BindingFlags.Instance); + private static MethodInfo s_invalidateEnclaveSession = s_sqlCommand.GetMethod("InvalidateEnclaveSession", BindingFlags.NonPublic | BindingFlags.Instance); +#if DEBUG + private static FieldInfo s_forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = + s_sqlCommand.GetField(@"_forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage", BindingFlags.NonPublic | BindingFlags.Static); +#endif public static PropertyInfo s_debugForceAsyncWriteDelay = s_sqlCommand.GetProperty("DebugForceAsyncWriteDelay", BindingFlags.NonPublic | BindingFlags.Static); public static FieldInfo s_sleepDuringTryFetchInputParameterEncryptionInfo = s_sqlCommand.GetField(@"_sleepDuringTryFetchInputParameterEncryptionInfo", BindingFlags.Static | BindingFlags.NonPublic); public static PropertyInfo s_isDescribeParameterEncryptionRPCCurrentlyInProgress = s_sqlCommand.GetProperty(@"IsDescribeParameterEncryptionRPCCurrentlyInProgress", BindingFlags.Instance | BindingFlags.NonPublic); @@ -31,6 +36,18 @@ internal static void CompletePendingReadWithFailure(SqlCommand command, int erro s_completePendingReadWithFailure.Invoke(command, new object[] { errorCode, resetForcePendingReadsToWait }); } + internal static void InvalidateEnclaveSession(SqlCommand command) + { + s_invalidateEnclaveSession.Invoke(command, null); + } + +#if DEBUG + internal static void ForceThrowDuringGenerateEnclavePackage(SqlCommand command) + { + s_forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage.SetValue(command, true); + } +#endif + internal static int ForceAsyncWriteDelay { get { return (int)s_debugForceAsyncWriteDelay.GetValue(null); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 70cae84d73..8a50cb8ae1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -72,13 +72,14 @@ private static void ConnectAndDisconnect(string connectionString, SqlCredential private static bool IsAccessTokenSetup() => DataTestUtility.IsAccessTokenSetup(); private static bool IsAADConnStringsSetup() => DataTestUtility.IsAADPasswordConnStrSetup(); private static bool IsManagedIdentitySetup() => DataTestUtility.ManagedIdentitySupported; + private static bool SupportsSystemAssignedManagedIdentity() => DataTestUtility.SupportsSystemAssignedManagedIdentity; [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup))] + [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] public static void KustoDatabaseTest() { // This is a sample Kusto database that can be connected by any AD account. - using SqlConnection connection = new SqlConnection("Data Source=help.kusto.windows.net; Authentication=Active Directory Default;Trust Server Certificate=True;"); + using SqlConnection connection = new SqlConnection($"Data Source=help.kusto.windows.net; Authentication=Active Directory Default;Trust Server Certificate=True;User ID = {DataTestUtility.UserManagedIdentityClientId};"); connection.Open(); Assert.True(connection.State == System.Data.ConnectionState.Open); } @@ -556,7 +557,7 @@ public static void ActiveDirectoryDefaultMustPass() { string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - "Authentication=ActiveDirectoryDefault;"; + $"Authentication=ActiveDirectoryDefault;User ID={DataTestUtility.UserManagedIdentityClientId};"; // Connection should be established using Managed Identity by default. ConnectAndDisconnect(connStr); @@ -612,7 +613,7 @@ public static void ConnectionSpeed() #region Managed Identity Authentication tests - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] + [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void SystemAssigned_ManagedIdentityTest() { string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; @@ -630,7 +631,7 @@ public static void UserAssigned_ManagedIdentityTest() ConnectAndDisconnect(connStr); } - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] + [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void AccessToken_SystemManagedIdentityTest() { string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; @@ -658,7 +659,7 @@ public static void AccessToken_UserManagedIdentityTest() } } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup))] + [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void Azure_SystemManagedIdentityTest() { string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; @@ -688,7 +689,7 @@ public static void Azure_UserManagedIdentityTest() } } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsAccessTokenSetup), nameof(IsManagedIdentitySetup))] + [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsAccessTokenSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void Azure_AccessToken_SystemManagedIdentityTest() { string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs index 489a414ce9..7675643382 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -362,7 +362,8 @@ public static void ConnectionStringPersistentInfoTest() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + // ConnectionOpenDisableRetry relies on error 4060 for automatic retry, which is not returned when using AAD auth + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.TcpConnectionStringDoesNotUseAadAuth))] public static void ConnectionOpenDisableRetry() { SqlConnectionStringBuilder connectionStringBuilder = new(DataTestUtility.TCPConnectionString) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index 283ddc5c63..0bc5b29806 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -10,92 +10,98 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public static class ConnectionSchemaTest { + public static bool CanRunSchemaTests() + { + return DataTestUtility.AreConnStringsSetup() && + // Tests switch to master database, which is not guaranteed when using AAD auth + DataTestUtility.TcpConnectionStringDoesNotUseAadAuth; + } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetTablesFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetProceduresFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetProcedureParametersFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetDatabasesFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetForeignKeysFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetIndexesFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetIndexColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetAllColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetColumnSetColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetUsersFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetViewsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetViewColumnsFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetUserDefinedTypesFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" }); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(nameof(CanRunSchemaTests))] public static void GetStructuredTypeMembersFromSchema() { VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" }); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs index f008d6b2fa..3f16a3a138 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using System.Xml; +using System.Xml.Linq; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -471,6 +472,37 @@ public static void InvalidCastExceptionStream(CommandBehavior behavior, Accessor } } +#if NETCOREAPP + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static async void ReadAsyncContentsCompletes() + { + string expectedXml = "This is a test string"; + string query = $"SELECT CAST('{expectedXml}' AS NVARCHAR(MAX))"; + + string returnedXml = null; + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using (SqlCommand command = new SqlCommand(query, connection)) + { + connection.Open(); + + await using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess).ConfigureAwait(false)) + { + while (await reader.ReadAsync().ConfigureAwait(false)) + { + using (TextReader textReader = reader.GetTextReader(0)) + using (XmlReader xmlReader = XmlReader.Create(textReader, new XmlReaderSettings() { Async = true })) + { + XDocument xdoc = await XDocument.LoadAsync(xmlReader, LoadOptions.None, default).ConfigureAwait(false); + returnedXml = xdoc.ToString(); + } + } + } + } + + Assert.Equal(expectedXml, returnedXml, StringComparer.Ordinal); + } +#endif + private static async Task ExecuteReader(SqlCommand command, CommandBehavior behavior, bool isExecuteAsync) => isExecuteAsync ? await command.ExecuteReaderAsync(behavior) : command.ExecuteReader(behavior); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs index c9dfe33bda..ce751c4d29 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs @@ -205,7 +205,13 @@ public static void ExceptionTests() public static void VariousExceptionTests() { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); - + // Strip the password in connection string if Authentication=Active Directory Managed Identity as it can not be used with a Password + if (builder.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity) + { + string[] removeKeys = { "Password", "PWD" }; + string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys); + builder = new SqlConnectionStringBuilder(connStr); + } // Test 1 - A SqlConnectionStringBuilder badBuilder = new SqlConnectionStringBuilder(builder.ConnectionString) { DataSource = badServer, ConnectTimeout = 1 }; @@ -219,11 +225,14 @@ public static void VariousExceptionTests() } // Test 1 - B - badBuilder = new SqlConnectionStringBuilder(builder.ConnectionString) { Password = string.Empty, IntegratedSecurity = false }; - using (var sqlConnection = new SqlConnection(badBuilder.ConnectionString)) + if (DataTestUtility.IsNotAzureServer()) { - string errorMessage = string.Format(CultureInfo.InvariantCulture, logonFailedErrorMessage, badBuilder.UserID); - VerifyConnectionFailure(() => sqlConnection.Open(), errorMessage, (ex) => VerifyException(ex, 1, 18456, 1, 14)); + badBuilder = new SqlConnectionStringBuilder(builder.ConnectionString) { Password = string.Empty, IntegratedSecurity = false }; + using (var sqlConnection = new SqlConnection(badBuilder.ConnectionString)) + { + string errorMessage = string.Format(CultureInfo.InvariantCulture, logonFailedErrorMessage, badBuilder.UserID); + VerifyConnectionFailure(() => sqlConnection.Open(), errorMessage, (ex) => VerifyException(ex, 1, 18456, 1, 14)); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs index 6edc9f00a6..1ae87217b6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -33,7 +33,10 @@ public static void ConnectToSQLWithInstanceNameTest() connection.Open(); connection.Close(); - if (builder.Encrypt != SqlConnectionEncryptOption.Strict) + // We can only connect via IP address if we aren't doing remote Kerberos or strict TLS + if (builder.Encrypt != SqlConnectionEncryptOption.Strict && + (!builder.IntegratedSecurity || hostname.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + hostname.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase))) { // Exercise the IP address-specific code in SSRP IPAddress[] addresses = Dns.GetHostAddresses(hostname); @@ -64,7 +67,6 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName)) { builder.DataSource = hostname + "\\" + instanceName; - using SqlConnection connection = new(builder.ConnectionString); connection.Open(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs index 6cd19714ae..654d020742 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs @@ -46,7 +46,13 @@ public static void IntegratedAuthenticationTest_ServerSPN() { SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); builder.IntegratedSecurity = true; - builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN()}"; + Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out int port, out string instanceName)); + // Build the SPN for the server we are connecting to + builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(hostname)}"; + if (!string.IsNullOrWhiteSpace(instanceName)) + { + builder.ServerSPN += ":" + instanceName; + } TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs index 0c06e6cf47..60e9b293f3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -16,6 +16,7 @@ private enum InfoType state } private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled(); + private static bool IsNativeSNI() => DataTestUtility.IsUsingNativeSNI(); private static bool IsLocalDbSharedInstanceSet() => DataTestUtility.IsLocalDbSharedInstanceSetup(); private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}"; private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." }; @@ -123,6 +124,47 @@ public static void LocalDBNamepipeMarsTest() #endregion + #region Failures + // ToDo: After adding shared memory support on managed SNI, the IsNativeSNI could be taken out + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP + [ConditionalTheory(nameof(IsLocalDBEnvironmentSet), nameof(IsNativeSNI))] + [InlineData("lpc:")] + public static void SharedMemoryAndSqlLocalDbConnectionTest(string prefix) + { + SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString); + stringBuilder.DataSource = prefix + stringBuilder.DataSource; + SqlException ex = Assert.Throws(() => ConnectionTest(stringBuilder.ConnectionString)); + Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 41 - Cannot open a Shared Memory connection to a remote SQL server)", ex.Message); + } + + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP + [InlineData("tcp:")] + [InlineData("np:")] + [InlineData("undefinded:")] + [ConditionalTheory(nameof(IsLocalDBEnvironmentSet))] + public static void PrefixAndSqlLocalDbConnectionTest(string prefix) + { + SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString); + stringBuilder.DataSource = prefix + stringBuilder.DataSource; + SqlException ex = Assert.Throws(() => ConnectionTest(stringBuilder.ConnectionString)); + Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)", ex.Message); + } + + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP + [ConditionalFact(nameof(IsLocalDBEnvironmentSet))] + public static void InvalidSqlLocalDbConnectionTest() + { + SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString); + stringBuilder.DataSource = stringBuilder.DataSource + "Invalid123"; + SqlException ex = Assert.Throws(() => ConnectionTest(stringBuilder.ConnectionString)); + Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 50 - Local Database Runtime error occurred.", ex.Message); + if (IsNativeSNI()) + { + Assert.Contains("The specified LocalDB instance does not exist.", ex.Message); + } + } + #endregion + private static void ConnectionWithMarsTest(string connectionString) { SqlConnectionStringBuilder builder = new(connectionString) @@ -178,13 +220,13 @@ private static void RestartLocalDB() { string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, InfoType.state); int count = 5; - while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase) && count>0) + while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase) && count > 0) { count--; state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, InfoType.state); Thread.Sleep(2000); } - if(state == null || state != "Running") + if (state == null || state != "Running") { throw new LocalDBNotStartedException(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs index ba83e9b22a..a9eb1b52a2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs @@ -71,7 +71,8 @@ private static readonly HashSet s_defaultTransientErrors 20, 0, -2, // Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding. - 207 // invalid column name + 207, // invalid column name + 18456 // Using managed identity in Azure Sql Server throws 18456 for non-existent database instead of 4060. }; internal static readonly string s_exceedErrMsgPattern = SystemDataResourceManager.Instance.SqlRetryLogic_RetryExceeded; @@ -117,7 +118,7 @@ public static IEnumerable GetConnectionAndRetryStrategy(int numberOfRe public static IEnumerable GetConnectionAndRetryStrategyInvalidCatalog(int numberOfRetries) { - return GetConnectionAndRetryStrategy(numberOfRetries, TimeSpan.FromSeconds(1), FilterSqlStatements.None, null, 250, false); + return GetConnectionAndRetryStrategy(numberOfRetries, TimeSpan.FromSeconds(1), FilterSqlStatements.None, null, 250, true); } public static IEnumerable GetConnectionAndRetryStrategyInvalidCommand(int numberOfRetries) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs index a5e3f901d4..67fd739f5f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs @@ -23,7 +23,8 @@ public class SqlConfigurationManagerReliabilityTest { InitialCatalog = SqlConnectionReliabilityTest.InvalidInitialCatalog, ConnectTimeout = 1 }.ConnectionString; #region Internal Functions - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + // Test relies on error 4060 for automatic retry, which is not returned when using AAD auth + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.TcpConnectionStringDoesNotUseAadAuth))] [InlineData(RetryLogicConfigHelper.RetryMethodName_Fix, RetryLogicConfigHelper.RetryMethodName_Inc)] [InlineData(RetryLogicConfigHelper.RetryMethodName_Inc, RetryLogicConfigHelper.RetryMethodName_Exp)] [InlineData(RetryLogicConfigHelper.RetryMethodName_Exp, RetryLogicConfigHelper.RetryMethodName_Fix)] @@ -129,7 +130,8 @@ public void InvalidRetryMethodName(string methodName) s_commandCRLTest.NoneRetriableExecuteFail(TcpCnnString, cmdProvider); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + // Test relies on error 4060 for automatic retry, which is not returned when using AAD auth + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.TcpConnectionStringDoesNotUseAadAuth))] [InlineData("InvalidRetrylogicTypeName")] [InlineData("")] [InlineData(null)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs index 624912f260..c78c060677 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs @@ -16,6 +16,7 @@ public class SqlConnectionReliabilityTest private readonly string _cancelErrMsgPattern = RetryLogicTestHelper.s_cancelErrMsgPattern; #region Sync + // Test relies on error 4060 for automatic retry, which is not reliable when using Azure or AAD auth [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public void ConnectionRetryOpenInvalidCatalogFailed(string cnnString, SqlRetryLogicBaseProvider provider) @@ -33,6 +34,7 @@ public void ConnectionRetryOpenInvalidCatalogFailed(string cnnString, SqlRetryLo } } + // Test relies on error 4060 for automatic retry, which is not reliable when using Azure or AAD auth [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public void ConnectionCancelRetryOpenInvalidCatalog(string cnnString, SqlRetryLogicBaseProvider provider) @@ -98,6 +100,7 @@ public void CreateDatabaseWhileTryingToConnect(string cnnString, SqlRetryLogicBa Assert.True(currentRetries > 0); } + [ActiveIssue(25147)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public void ConcurrentExecution(string cnnString, SqlRetryLogicBaseProvider provider) @@ -154,6 +157,7 @@ public void DefaultOpenWithoutRetry(string connectionString, SqlRetryLogicBasePr #endregion #region Async + // Test relies on error 4060 for automatic retry, which is not reliable when using Azure or AAD auth [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 5 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public async void ConnectionRetryOpenAsyncInvalidCatalogFailed(string cnnString, SqlRetryLogicBaseProvider provider) @@ -171,7 +175,8 @@ public async void ConnectionRetryOpenAsyncInvalidCatalogFailed(string cnnString, } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + // Test relies on error 4060 for automatic retry, which is not returned when using AAD auth + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.TcpConnectionStringDoesNotUseAadAuth))] [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public async void ConnectionCancelRetryOpenAsyncInvalidCatalog(string cnnString, SqlRetryLogicBaseProvider provider) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs new file mode 100644 index 0000000000..3782dc58a0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs @@ -0,0 +1,200 @@ +using System; +using System.Data; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.SqlBulkCopyTest +{ + /// + /// Validates SqlBulkCopy functionality when working with UTF-8 encoded data. + /// Ensures that data copied from a UTF-8 source table to a destination table retains its encoding and content integrity. + /// + public sealed class TestBulkCopyWithUtf8 : IDisposable + { + private static bool IsAzureServer() => !DataTestUtility.IsNotAzureServer(); + private static bool IsNotAzureSynapse => DataTestUtility.IsNotAzureSynapse(); + private static bool AreConnectionStringsSetup() => DataTestUtility.AreConnStringsSetup(); + private static string s_sourceTable = DataTestUtility.GetUniqueName("SourceTableForUTF8Data"); + private static string s_destinationTable = DataTestUtility.GetUniqueName("DestinationTableForUTF8Data"); + private static string s_testValue = "test"; + private static byte[] s_testValueInUtf8Bytes = new byte[] { 0x74, 0x65, 0x73, 0x74 }; + private static readonly string s_insertQuery = $"INSERT INTO {s_sourceTable} VALUES('{s_testValue}')"; + + /// + /// Constructor: Initializes and populates source and destination tables required for the tests. + /// + public TestBulkCopyWithUtf8() + { + using SqlConnection sourceConnection = new SqlConnection(GetConnectionString(true)); + sourceConnection.Open(); + SetupTables(sourceConnection, s_sourceTable, s_destinationTable, s_insertQuery); + } + + /// + /// Cleanup method to drop tables after test completion. + /// + public void Dispose() + { + using SqlConnection connection = new SqlConnection(GetConnectionString(true)); + connection.Open(); + DataTestUtility.DropTable(connection, s_sourceTable); + DataTestUtility.DropTable(connection, s_destinationTable); + connection.Close(); + } + + /// + /// Builds a connection string with or without Multiple Active Result Sets (MARS) property. + /// + private string GetConnectionString(bool enableMars) + { + return new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + MultipleActiveResultSets = enableMars + }.ConnectionString; + } + + /// + /// Creates source and destination tables with a varchar(max) column with a collation setting + /// that stores the data in UTF8 encoding and inserts the data in the source table. + /// + private void SetupTables(SqlConnection connection, string sourceTable, string destinationTable, string insertQuery) + { + string columnDefinition = "(str_col varchar(max) COLLATE Latin1_General_100_CS_AS_KS_WS_SC_UTF8)"; + DataTestUtility.CreateTable(connection, sourceTable, columnDefinition); + DataTestUtility.CreateTable(connection, destinationTable, columnDefinition); + using SqlCommand insertCommand = connection.CreateCommand(); + insertCommand.CommandText = insertQuery; + Helpers.TryExecute(insertCommand, insertQuery); + } + + /// + /// Synchronous test case: Validates that data copied using SqlBulkCopy matches UTF-8 byte sequence for test value. + /// Tested with MARS enabled and disabled, and with streaming enabled and disabled. + /// + [ConditionalTheory(typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureServer), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void BulkCopy_Utf8Data_ShouldMatchSource(bool isMarsEnabled, bool enableStreaming) + { + // Setup connections for source and destination tables + string connectionString = GetConnectionString(isMarsEnabled); + using SqlConnection sourceConnection = new SqlConnection(connectionString); + sourceConnection.Open(); + using SqlConnection destinationConnection = new SqlConnection(connectionString); + destinationConnection.Open(); + + // Read data from source table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT str_col FROM {s_sourceTable}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(CommandBehavior.SequentialAccess); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_destinationTable}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + EnableStreaming = enableStreaming, + DestinationTableName = s_destinationTable + }; + + try + { + // Perform bulk copy from source to destination table + bulkCopy.WriteToServer(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 1 row from the source table has been copied into our destination table. + Assert.Equal(1, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT cast(str_col as varbinary) FROM {s_destinationTable}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(CommandBehavior.SequentialAccess); + + // Verify that we have data in the destination table + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + + // Read the value of the column as SqlBinary. + byte[] actualBytes = verifyReader.GetSqlBinary(0).Value; + + // Verify that the byte array matches the expected UTF-8 byte sequence + Assert.Equal(s_testValueInUtf8Bytes.Length, actualBytes.Length); + Assert.Equal(s_testValueInUtf8Bytes, actualBytes); + } + + /// + /// Asynchronous version of the testcase BulkCopy_Utf8Data_ShouldMatchSource + /// + [ConditionalTheory(typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureServer), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task BulkCopy_Utf8Data_ShouldMatchSource_Async(bool isMarsEnabled, bool enableStreaming) + { + // Setup connections for source and destination tables + string connectionString = GetConnectionString(isMarsEnabled); + using SqlConnection sourceConnection = new SqlConnection(connectionString); + await sourceConnection.OpenAsync(); + using SqlConnection destinationConnection = new SqlConnection(connectionString); + await destinationConnection.OpenAsync(); + + // Read data from source table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT str_col FROM {s_sourceTable}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_destinationTable}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + EnableStreaming = enableStreaming, + DestinationTableName = s_destinationTable + }; + + try + { + // Perform bulk copy from source to destination table + await bulkCopy.WriteToServerAsync(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 1 row from the source table has been copied into our destination table. + Assert.Equal(1, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT cast(str_col as varbinary) FROM {s_destinationTable}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + // Verify that we have data in the destination table + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + + // Read the value of the column as SqlBinary. + byte[] actualBytes = verifyReader.GetSqlBinary(0).Value; + + // Verify that the byte array matches the expected UTF-8 byte sequence + Assert.Equal(s_testValueInUtf8Bytes.Length, actualBytes.Length); + Assert.Equal(s_testValueInUtf8Bytes, actualBytes); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs index 34061606f4..ade9f41844 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs @@ -47,11 +47,10 @@ public static void TestManualEnlistment_Enlist_TxScopeComplete() RunTestSet(TestCase_ManualEnlistment_Enlist_TxScopeComplete); } - [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSupportingDistributedTransactions))] public static void TestEnlistmentPrepare_TxScopeComplete() { - try + Assert.Throws(() => { using TransactionScope txScope = new(TransactionScopeOption.RequiresNew, new TransactionOptions() { @@ -64,11 +63,7 @@ public static void TestEnlistmentPrepare_TxScopeComplete() System.Transactions.Transaction.Current.EnlistDurable(EnlistmentForPrepare.s_id, new EnlistmentForPrepare(), EnlistmentOptions.None); txScope.Complete(); Assert.False(true, "Expected exception not thrown."); - } - catch (Exception e) - { - Assert.True(e is TransactionAbortedException); - } + }); } private static void TestCase_AutoEnlistment_TxScopeComplete() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs index 9d1c5c6b5f..da1d0cdeab 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs @@ -46,8 +46,7 @@ public static class TransactionTest public static void ReadNextQueryAfterTxAbortedPoolEnabled(string connString) => ReadNextQueryAfterTxAbortedTest(connString); - // Azure SQL has no DTC support - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(PoolDisabledConnectionStrings))] public static void ReadNextQueryAfterTxAbortedPoolDisabled(string connString) => ReadNextQueryAfterTxAbortedTest(connString); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs index 6a936ac5a1..52bf5c30f4 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs @@ -24,7 +24,6 @@ public static class SqlDbManager private const string TCPConnectionString = "TCPConnectionString"; private const string NPConnectionString = "NPConnectionString"; private const string TCPConnectionStringAASSGX = "TCPConnectionStringAASSGX"; - private const string TCPConnectionStringAASVBS = "TCPConnectionStringAASVBS"; private const string TCPConnectionStringHGSVBS = "TCPConnectionStringHGSVBS"; /// @@ -124,10 +123,6 @@ private static void LoadActiveConnectionStrings() { s_activeConnectionStrings.Add(TCPConnectionStringAASSGX, s_configJson.TCPConnectionStringAASSGX); } - if (!string.IsNullOrEmpty(s_configJson.TCPConnectionStringAASVBS)) - { - s_activeConnectionStrings.Add(TCPConnectionStringAASVBS, s_configJson.TCPConnectionStringAASVBS); - } if (!string.IsNullOrEmpty(s_configJson.TCPConnectionStringHGSVBS)) { s_activeConnectionStrings.Add(TCPConnectionStringHGSVBS, s_configJson.TCPConnectionStringHGSVBS); @@ -148,9 +143,6 @@ private static void UpdateConfig(string key, SqlConnectionStringBuilder builder) case TCPConnectionStringAASSGX: s_configJson.TCPConnectionStringAASSGX = builder.ConnectionString; break; - case TCPConnectionStringAASVBS: - s_configJson.TCPConnectionStringAASVBS = builder.ConnectionString; - break; case TCPConnectionStringHGSVBS: s_configJson.TCPConnectionStringHGSVBS = builder.ConnectionString; break; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index 47aad75eec..882232b123 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -13,7 +13,6 @@ public class Config public string TCPConnectionString = null; public string NPConnectionString = null; public string TCPConnectionStringHGSVBS = null; - public string TCPConnectionStringAASVBS = null; public string TCPConnectionStringNoneVBS = null; public string TCPConnectionStringAASSGX = null; public string AADAuthorityURL = null; @@ -22,8 +21,6 @@ public class Config public string AADServicePrincipalSecret = null; public string AzureKeyVaultURL = null; public string AzureKeyVaultTenantId = null; - public string AzureKeyVaultClientId = null; - public string AzureKeyVaultClientSecret = null; public string LocalDbAppName = null; public string LocalDbSharedInstanceName = null; public bool EnclaveEnabled = false; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json index 37f7c9184d..7ee2ec5d77 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json @@ -2,7 +2,6 @@ "TCPConnectionString": "Data Source=tcp:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", "NPConnectionString": "Data Source=np:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", "TCPConnectionStringHGSVBS": "", - "TCPConnectionStringAASVBS": "", "TCPConnectionStringNoneVBS": "", "TCPConnectionStringAASSGX": "", "EnclaveEnabled": false, @@ -13,8 +12,6 @@ "AADServicePrincipalSecret": "", "AzureKeyVaultURL": "", "AzureKeyVaultTenantId": "", - "AzureKeyVaultClientId": "", - "AzureKeyVaultClientSecret": "", "SupportsIntegratedSecurity": true, "LocalDbAppName": "", "LocalDbSharedInstanceName": "", diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs index a637e6bcc4..019fefd907 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using Microsoft.SqlServer.TDS.PreLogin; namespace Microsoft.SqlServer.TDS.EndPoint { @@ -68,8 +69,19 @@ public void Run() { case TDSMessageType.PreLogin: { + if (Session.Encryption == TDSEncryptionType.None) + { + (MessageBeingReceived[0] as TDSPreLoginToken).Encryption = TDSPreLoginTokenEncryptionType.None; + } + // Call into the subscriber to process the packet responseMessages = Server.OnPreLoginRequest(Session, MessageBeingReceived); + + if (Session.Encryption == TDSEncryptionType.None) + { + DisableTransportEncryption(); + } + break; } case TDSMessageType.TDS7Login: diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginToken.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginToken.cs index 7a5288f1c8..f5ca465fc0 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginToken.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginToken.cs @@ -331,7 +331,10 @@ public override void Deflate(Stream destination) options.Add(new TDSPreLoginTokenOption(TDSPreLoginTokenOptionType.NonceOption, (ushort)Nonce.Length)); } - options.Add(new TDSPreLoginTokenOption(TDSPreLoginTokenOptionType.Encryption, 1)); + if (Encryption != TDSPreLoginTokenEncryptionType.None) + { + options.Add(new TDSPreLoginTokenOption(TDSPreLoginTokenOptionType.Encryption, 1)); + } options.Add(new TDSPreLoginTokenOption(TDSPreLoginTokenOptionType.Terminator, 0)); // Calculate the total size of the token metadata diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginTokenEncryptionType.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginTokenEncryptionType.cs index fadb814579..82e69382e5 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginTokenEncryptionType.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/PreLogin/TDSPreLoginTokenEncryptionType.cs @@ -12,6 +12,7 @@ public enum TDSPreLoginTokenEncryptionType : byte Off = 0x00, On = 0x01, NotSupported = 0x02, - Required = 0x03 + Required = 0x03, + None = 0x10 } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSEncryptionType.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSEncryptionType.cs index 348605b894..4716273d73 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSEncryptionType.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSEncryptionType.cs @@ -22,6 +22,11 @@ public enum TDSEncryptionType /// /// Encryption of the entire session /// - Full + Full, + + /// + /// Excludes encryption option in Pre-Login response + /// + None } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSUtilities.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSUtilities.cs index a4bf123d6a..d8a0c292c5 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSUtilities.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSUtilities.cs @@ -261,11 +261,19 @@ public static TDSPreLoginTokenEncryptionType GetEncryptionResponse(TDSPreLoginTo { return TDSPreLoginTokenEncryptionType.On; } + else if (server == TDSPreLoginTokenEncryptionType.None) + { + return TDSPreLoginTokenEncryptionType.None; + } else { throw new ArgumentException("Server is configured to not support encryption", "server"); } } + else if (client == TDSPreLoginTokenEncryptionType.None) + { + return TDSPreLoginTokenEncryptionType.None; + } // This case is not documented so pick a default return TDSPreLoginTokenEncryptionType.Off; @@ -313,6 +321,10 @@ public static TDSEncryptionType ResolveEncryption(TDSPreLoginTokenEncryptionType return TDSEncryptionType.LoginOnly; } } + else if (client == TDSPreLoginTokenEncryptionType.None) + { + return TDSEncryptionType.None; + } // Full encryption is required return TDSEncryptionType.Full; diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index dafc9a4dbe..2808094a25 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -40,9 +40,6 @@ Strings.Designer.cs - - - diff --git a/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj b/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj index 8bfcd7ad1e..b0320948d3 100644 --- a/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj +++ b/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj @@ -15,6 +15,7 @@ + diff --git a/tools/props/Versions.props b/tools/props/Versions.props index b9a5cb84d7..a2b1d7273a 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -1,12 +1,15 @@ - 1.0.0.0 + 5.1.7 + 0 + $(MdsVersionDefault).$(BuildNumber) + $(AssemblyFileVersion) 5.0.0.0 - 5.1.0-dev + $(MdsVersionDefault)-dev $(NugetPackageVersion) @@ -20,25 +23,25 @@ - 5.1.0 + 5.1.2 - 1.7.0 - 4.47.2 - 6.24.0 - 6.24.0 + 1.11.4 + 4.61.3 + 6.35.0 + 6.35.0 4.5.1 - 6.0.0 - 1.1.0 + 6.0.1 + 6.0.11 5.0.0 - 5.1.0 + 5.1.2 6.0.1 1.0.0 - 6.0.0 + 6.0.1 6.0.1 6.0.0 5.0.0 @@ -52,19 +55,17 @@ - [1.25.0,2.0.0) + [1.38.0,2.0.0) [4.4.0,5.0.0) - 6.0.1 + 6.0.3 3.1.6 - 5.2.9 17.4.1 13.0.1 4.3.0 6.0.1 - 6.24.0 2.4.2 2.4.5 7.0.0-beta.22316.1 @@ -73,6 +74,7 @@ 10.50.1600.1 0.13.2 6.0.0 + 6.0.1 $(NugetPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 0c56f9f8c4..7e45b87cf0 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -28,60 +28,61 @@ When using NuGet 3.x this package requires at least version 3.4. sqlclient microsoft.data.sqlclient - - - - - + + + + + - + + - - - - - + + + + + - + - + - - - - - + + + + + - + - - - - - + + + + + - + diff --git a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec index e243d20e26..2a18f80358 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -25,25 +25,25 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti sqlclient microsoft.data.sqlclient azurekeyvaultprovider akvprovider alwaysencrypted - - - + + + - + - - - + + + - + - - - + + + - + @@ -55,32 +55,32 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti - - - - - + + + + + - - - + + + - - - + + + - + - - - + + + - - - + + + - + diff --git a/tools/targets/RepositoryInfo.targets b/tools/targets/RepositoryInfo.targets new file mode 100644 index 0000000000..dec4f95206 --- /dev/null +++ b/tools/targets/RepositoryInfo.targets @@ -0,0 +1,40 @@ + + + + + + false + false + false + + + + + + + + <_TranslateUrlPattern>https://[^/]+/ADO.Net/_git/([^/-]+)-(.+) + <_TranslateUrlReplacement>https://github.com/dotnet/SqlClient + + + + + + $([System.Text.RegularExpressions.Regex]::Replace($(ScmRepositoryUrl), $(_TranslateUrlPattern), $(_TranslateUrlReplacement))) + + + + + + + $([System.Text.RegularExpressions.Regex]::Replace(%(SourceRoot.ScmRepositoryUrl), + $(_TranslateUrlPattern), $(_TranslateUrlReplacement))) + + + + +