diff --git a/.gitignore b/.gitignore index daeaf68c7a..bb6f58f9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -353,6 +353,9 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ +# JetBrains Rider (cross platform .NET IDE) working folder +.idea/ + # Ionide (cross platform F# VS Code tools) working folder .ionide/ 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..ecd07a41c9 --- /dev/null +++ b/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml @@ -0,0 +1,64 @@ +################################################################################# +# 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. # +################################################################################# +jobs: +- job: build_signed_akv_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' + + - 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 private server + - template: ../steps/publish-symbols-step.yml@self + parameters: + SymAccount: $(PrivateSymAccount) + referenceType: package + symbolsVersion: ${{variables.AKVNuGetPackageVersion }} + product: AKV + + # Publish symbols to public server + - template: ../steps/publish-symbols-step.yml@self + parameters: + SymAccount: $(PublicSymAccount) + referenceType: package + symbolsVersion: ${{variables.AKVNuGetPackageVersion }} + product: AKV diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 3244a9a0dd..26ab9fa236 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -17,59 +17,34 @@ jobs: type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs variables: - - template: ../../../libraries/variables.yml@self + - template: ../../../libraries/variables.yml@self steps: - script: SET displayName: 'Print Environment Variables' + - 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 - - powershell: | - $software = '${{parameters.softwareFolder}}' - md $software - md $software\win - md $software\win\net46 - md $software\win\net6.0 - md $software\win\net8.0 - md $software\win\netstandard2.0 - md $software\win\netstandard2.1 - - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\Microsoft.Data.SqlClient.dll" "$software\win\net46" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\net6.0\Microsoft.Data.SqlClient.dll" "$software\win\net6.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\net8.0\Microsoft.Data.SqlClient.dll" "$software\win\net8.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\netstandard2.0\Microsoft.Data.SqlClient.dll" "$software\win\netstandard2.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\netstandard2.1\Microsoft.Data.SqlClient.dll" "$software\win\netstandard2.1" -recurse - - $symbols = '${{parameters.symbolsFolder}}' - md $symbols - md $symbols\win - md $symbols\win\net46 - md $symbols\win\net6.0 - md $symbols\win\net8.0 - md $symbols\win\netstandard2.0 - md $symbols\win\netstandard2.1 - - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\Microsoft.Data.SqlClient.pdb" "$symbols\win\net46" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\net6.0\Microsoft.Data.SqlClient.pdb" "$symbols\win\net6.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\net8.0\Microsoft.Data.SqlClient.pdb" "$symbols\win\net8.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\netstandard2.0\Microsoft.Data.SqlClient.pdb" "$symbols\win\netstandard2.0" -recurse - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\netstandard2.1\Microsoft.Data.SqlClient.pdb" "$symbols\win\netstandard2.1" -recurse - - Write-Host "Artifacts fetched for testing" - dir - Get-Location - displayName: 'Prepare Arifacts for Testing' + + - template: ../steps/copy-dlls-for-test-step.yml@self + parameters: + product: MDS + # Publish symbols to private server - template: ../steps/publish-symbols-step.yml@self parameters: 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 index 0739e543cc..f764a55201 100644 --- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -28,8 +28,7 @@ jobs: vmImage: 'ADO-MMS22-SQL19' variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: ../../../libraries/build-variables.yml@self - - template: ../../../libraries/validation-variables.yml@self + - template: ../../../libraries/mds-validation-variables.yml@self steps: - template: ../steps/pre-build-step.yml diff --git a/eng/pipelines/common/templates/jobs/verify-signed-package-job.yml b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml similarity index 98% rename from eng/pipelines/common/templates/jobs/verify-signed-package-job.yml rename to eng/pipelines/common/templates/jobs/validate-signed-package-job.yml index e77d2596fd..9f12f42e92 100644 --- a/eng/pipelines/common/templates/jobs/verify-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -34,7 +34,7 @@ parameters: default: $(AssemblyFileVersion) jobs: -- job: verify_signed_package +- job: validate_signed_package ${{ if ne(parameters.dependsOn, '')}}: dependsOn: '${{parameters.dependsOn }}' pool: @@ -44,8 +44,8 @@ jobs: vmImage: 'ADO-MMS22-SQL19' variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: ../../../libraries/build-variables.yml@self - - template: ../../../libraries/validation-variables.yml@self + - template: ../../../libraries/mds-validation-variables.yml@self + - name: pathToDownloadedNuget # path to the downloaded nuget files value: $(Pipeline.Workspace)\${{parameters.packageFolderName }} 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 index c5709114ac..f61dd48767 100644 --- 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 @@ -12,15 +12,50 @@ parameters: 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 +- 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:AssemblyOriginatorKeyFile=$(Agent.TempDirectory)\netfxKeypair.snk' + msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SignAssembly=true -p:AssemblyOriginatorKeyFile=$(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:SignAssembly=true -p:AssemblyOriginatorKeyFile=$(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:SignAssembly=true -p:AssemblyOriginatorKeyFile=$(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/code-analyze-step.yml b/eng/pipelines/common/templates/steps/code-analyze-step.yml index ec6a552fcb..92be8eabf6 100644 --- a/eng/pipelines/common/templates/steps/code-analyze-step.yml +++ b/eng/pipelines/common/templates/steps/code-analyze-step.yml @@ -14,15 +14,35 @@ parameters: 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')) }}: - - 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' + - ${{ 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' + - ${{ 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' - ${{ if or(eq(parameters.analyzeType, 'inspect'), eq(parameters.analyzeType, 'all')) }}: - task: securedevelopmentteam.vss-secure-development-tools.build-task-codeinspector.CodeInspector@2 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..6b0c1fa3ab --- /dev/null +++ b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml @@ -0,0 +1,108 @@ +################################################################################# +# 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 + - net8.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\$tf\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\$tf\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/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml index 91b4ee72e1..3736c0e828 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -23,6 +23,12 @@ parameters: - name: Configuration type: string default: '$(Configuration)' + + - name: referenceType + default: project + values: + - project + - package steps: - task: NuGetToolInstaller@1 @@ -37,4 +43,4 @@ steps: displayName: 'NuGet pack with snupkg' inputs: command: custom - arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=${{parameters.CommitHead}};Configuration=${{parameters.Configuration}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=${{parameters.CommitHead}};Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index 8dbb89db33..ea48c87ab8 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -11,19 +11,53 @@ parameters: type: string default: '$(PublishSymbols)' + - name: symbolsVersion + type: string + default: '$(NuGetPackageVersion)' + + - 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 ${{parameters.SymAccount}}' -- task: PublishSymbols@2 - displayName: 'Publish symbols path' - inputs: - SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\Project\bin' - SearchPattern: | - Windows_NT/$(Configuration).AnyCPU/**/Microsoft.Data.SqlClient.pdb - Unix/$(Configuration).AnyCPU/**/Microsoft.Data.SqlClient.pdb - IndexSources: false - SymbolServerType: TeamServices - SymbolsMaximumWaitTime: 60 - SymbolsProduct: Microsoft.Data.SqlClient - SymbolsVersion: '$(NuGetPackageVersion)' - condition: and(succeeded(), eq('${{ parameters.PublishSymbols }}', 'true')) + +- ${{ if eq(parameters.product, 'MDS') }}: + - task: PublishSymbols@2 + displayName: 'Publish symbols path' + 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 + SymbolsProduct: Microsoft.Data.SqlClient + SymbolsVersion: '{{parameters.symbolsVersion }}' + condition: and(succeeded(), eq('${{ parameters.PublishSymbols }}', 'true')) + +- ${{ if eq(parameters.product, 'AKV') }}: + - task: PublishSymbols@2 + displayName: 'Publish symbols path' + 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 + SymbolsProduct: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + SymbolsVersion: '{{parameters.symbolsVersion }}' + condition: and(succeeded(), eq('${{ parameters.PublishSymbols }}', 'true')) 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 index 61bd394af4..5258a3941d 100644 --- 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 @@ -27,13 +27,13 @@ steps: #Set the Nuget.config file in the project to use extracted package $rootFolder = Get-location - [Xml] $nugetConfig = Get-Content -Path "src\Nuget.config" + [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\src\Nuget.config") + $nugetConfig.Save("$rootFolder\Nuget.config") displayName: 'Update NuGet config file to read from Nuget folder' - task: MSBuild@1 diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index c9c1f9d8db..4d64fc502d 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -5,7 +5,27 @@ ################################################################################# name: $(Year:YY)$(DayOfYear)$(Rev:.rr) -trigger: none # https://aka.ms/obpipelines/triggers +trigger: + branches: + include: + - internal/main + paths: + exclude: + - src + - eng + - tools + - .config + - build.proj + - Nuget.config + - '*.cmd' + - '*.sh' + +schedules: +- cron: '30 21 * * 0' + displayName: Weekly Sunday 9:30 pm Build + branches: + include: + - internal/main parameters: # parameters are shown up in ADO UI in a build queue time - name: 'debug' @@ -16,7 +36,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time variables: - template: /eng/pipelines/libraries/variables.yml@self - name: packageFolderName - value: drop_build_build_signed_package + value: drop_buildMDS_build_signed_package resources: repositories: @@ -70,17 +90,24 @@ extends: tsaOptionsPath: $(REPOROOT)\.config\tsaoptions.json disableLegacyManifest: true stages: - - stage: build + - stage: buildAKV + jobs: + - template: eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml@self + parameters: + symbolsFolder: $(symbolsFolder) + softwareFolder: $(softwareFolder) + + - stage: buildMDS jobs: - template: eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self parameters: symbolsFolder: $(symbolsFolder) softwareFolder: $(softwareFolder) - + - stage: package_validation - dependsOn: build + dependsOn: buildMDS jobs: - - template: eng/pipelines/common/templates/jobs/verify-signed-package-job.yml@self + - template: eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self parameters: packageFolderName: $(packageFolderName) downloadPackageStep: @@ -88,6 +115,7 @@ extends: artifact: $(packageFolderName) patterns: '**/*.*nupkg' displayName: 'Download NuGet Package' + - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self parameters: packageFolderName: $(packageFolderName) diff --git a/eng/pipelines/libraries/akv-variables.yml b/eng/pipelines/libraries/akv-variables.yml new file mode 100644 index 0000000000..259ae19dba --- /dev/null +++ b/eng/pipelines/libraries/akv-variables.yml @@ -0,0 +1,15 @@ +################################################################################# +# 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: 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 index 613c03a02b..1c26f26a69 100644 --- a/eng/pipelines/libraries/build-variables.yml +++ b/eng/pipelines/libraries/build-variables.yml @@ -5,22 +5,6 @@ ################################################################################# variables: - - group: Release Variables - - name: NugetPackageVersion - value: $(Major).$(Minor).$(Patch) - - name: AssemblyFileVersion - value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' - - name: Configuration - value: Release - - name: CommitHead - value: '' # the value will be extracted from the repo's head - - name: REPOROOT - value: $(Build.SourcesDirectory) - - name: nuspecPath - value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' - - name: softwareFolder - value: $(REPOROOT)/software - - name: symbolsFolder - value: $(REPOROOT)/symbols - - name: artifactDirectory - value: '$(REPOROOT)/packages' + - 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..fb85f70d36 --- /dev/null +++ b/eng/pipelines/libraries/common-variables.yml @@ -0,0 +1,19 @@ +################################################################################# +# 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: + - 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/validation-variables.yml b/eng/pipelines/libraries/mds-validation-variables.yml similarity index 94% rename from eng/pipelines/libraries/validation-variables.yml rename to eng/pipelines/libraries/mds-validation-variables.yml index 32c008ef89..3c711364a3 100644 --- a/eng/pipelines/libraries/validation-variables.yml +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -5,7 +5,9 @@ ################################################################################# variables: - - template: build-variables.yml@self + - template: common-variables.yml@self + - template: mds-variables.yml@self + - name: TempFolderName # extract the nuget package here value: temp - name: extractedNugetPath diff --git a/eng/pipelines/libraries/mds-variables.yml b/eng/pipelines/libraries/mds-variables.yml new file mode 100644 index 0000000000..3da3172fa3 --- /dev/null +++ b/eng/pipelines/libraries/mds-variables.yml @@ -0,0 +1,15 @@ +################################################################################# +# 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: 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/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs index f7a0363f27..7f15666951 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs @@ -10,8 +10,6 @@ namespace Microsoft.Data.SqlClient { internal sealed partial class TdsParser { - private static volatile bool s_fSSPILoaded = false; // bool to indicate whether library has been loaded - internal void PostReadAsyncForMars() { if (TdsParserStateObjectFactory.UseManagedSNI) @@ -43,37 +41,7 @@ internal void PostReadAsyncForMars() _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); ThrowExceptionAndWarning(_physicalStateObj); } - } - - private void LoadSSPILibrary() - { - if (TdsParserStateObjectFactory.UseManagedSNI) - return; - // Outer check so we don't acquire lock once it's loaded. - if (!s_fSSPILoaded) - { - lock (s_tdsParserLock) - { - // re-check inside lock - if (!s_fSSPILoaded) - { - // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded. - uint maxLength = 0; - - if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength)) - SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE); - - s_maxSSPILength = maxLength; - s_fSSPILoaded = true; - } - } - } - - if (s_maxSSPILength > int.MaxValue) - { - throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 - } - } + } private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersion) { 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 7e04f6b9c5..4d731102fe 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 @@ -55,6 +55,8 @@ internal static void Assert(string message) private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); + private SSPIContextProvider _authenticationProvider; + internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); internal int ObjectID => _objectID; @@ -146,16 +148,9 @@ internal static void Assert(string message) // NIC address caching private static byte[] s_nicAddress; // cache the NIC address from the registry - // SSPI variables - - private volatile static uint s_maxSSPILength = 0; // variable to hold max SSPI data size, keep for token from server - // textptr sequence private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - private static object s_tdsParserLock = new object(); - - // XML metadata substitute sequence private static readonly byte[] s_xmlMetadataSubstituteSequence = { 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; @@ -422,11 +417,12 @@ internal void Connect( } _sniSpnBuffer = null; + _authenticationProvider = null; // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - LoadSSPILibrary(); + _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } @@ -472,6 +468,8 @@ internal void Connect( hostNameInCertificate, serverCertificateFilename); + _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); + if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -569,6 +567,8 @@ internal void Connect( hostNameInCertificate, serverCertificateFilename); + _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); + if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -1286,7 +1286,7 @@ private void FireInfoMessageEvent(SqlConnection connection, SqlCommand command, sqlErs.Add(error); - SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler, innerException:null, batchCommand: command?.GetCurrentBatchCommand()); + SqlException exc = SqlException.CreateException(sqlErs, serverVersion, _connHandler, innerException: null, batchCommand: command?.GetCurrentBatchCommand()); bool notified; connection.OnInfoMessage(new SqlInfoMessageEventArgs(exc), out notified); @@ -4001,7 +4001,7 @@ internal bool TryProcessError(byte token, TdsParserStateObject stateObj, SqlComm { batchIndex = command.GetCurrentBatchIndex(); } - error = new SqlError(number, state, errorClass, _server, message, procedure, line,exception: null, batchIndex: batchIndex); + error = new SqlError(number, state, errorClass, _server, message, procedure, line, exception: null, batchIndex: batchIndex); return true; } @@ -5744,7 +5744,7 @@ private bool TryReadSqlStringValue(SqlBuffer value, byte type, int length, Encod s = ""; } } - + if (buffIsRented) { // do not use clearArray:true on the rented array because it can be massively larger @@ -8108,165 +8108,6 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } - internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, SqlConnectionEncryptOption encrypt) - { - _physicalStateObj.SetTimeoutSeconds(rec.timeout); - - Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); - Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); - - Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); - Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); - Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); - - Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); - - Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value"); - - Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password"); - Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password"); - Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value"); - - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth); - - // get the password up front to use in sspi logic below - byte[] encryptedPassword = null; - byte[] encryptedChangePassword = null; - int encryptedPasswordLengthInBytes; - int encryptedChangePasswordLengthInBytes; - bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None); - - string userName; - - if (rec.credential != null) - { - userName = rec.credential.UserId; - encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2; - } - else - { - userName = rec.userName; - encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password); - encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte - } - - if (rec.newSecurePassword != null) - { - encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2; - } - else - { - encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword); - encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length; - } - - // set the message type - _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7; - - // length in bytes - int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN; - - string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; - Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec"); - - // add up variable-len portions (multiply by 2 for byte len of char strings) - // - checked - { - length += (rec.hostName.Length + rec.applicationName.Length + - rec.serverName.Length + clientInterfaceName.Length + - rec.language.Length + rec.database.Length + - rec.attachDBFilename.Length) * 2; - if (useFeatureExt) - { - length += 4; - } - } - - // allocate memory for SSPI variables - byte[] rentedSSPIBuff = null; - byte[] outSSPIBuff = null; - uint outSSPILength = 0; - - // only add lengths of password and username if not using SSPI or requesting federated authentication info - if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) - { - checked - { - length += (userName.Length * 2) + encryptedPasswordLengthInBytes - + encryptedChangePasswordLengthInBytes; - } - } - else - { - if (rec.useSSPI) - { - // now allocate proper length of buffer, and set length - rentedSSPIBuff = ArrayPool.Shared.Rent((int)s_maxSSPILength); - outSSPIBuff = rentedSSPIBuff; - outSSPILength = s_maxSSPILength; - - // Call helper function for SSPI data and actual length. - // Since we don't have SSPI data from the server, send null for the - // byte[] buffer and 0 for the int length. - Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); - _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; - - SSPIData(null, 0, ref outSSPIBuff, ref outSSPILength); - - if (outSSPILength > int.MaxValue) - { - throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 - } - _physicalStateObj.SniContext = SniContext.Snix_Login; - - checked - { - length += (int)outSSPILength; - } - } - } - - int feOffset = length; - // calculate and reserve the required bytes for the featureEx - length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); - - WriteLoginData(rec, - requestedFeatures, - recoverySessionData, - fedAuthFeatureExtensionData, - encrypt, - encryptedPassword, - encryptedChangePassword, - encryptedPasswordLengthInBytes, - encryptedChangePasswordLengthInBytes, - useFeatureExt, - userName, - length, - feOffset, - clientInterfaceName, - outSSPIBuff, - outSSPILength); - - if (rentedSSPIBuff != null) - { - ArrayPool.Shared.Return(rentedSSPIBuff, clearArray: true); - } - - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.ResetSecurePasswordsInformation(); - _physicalStateObj.HasPendingData = true; - _physicalStateObj._messageStatus = 0; - }// tdsLogin - private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -8617,81 +8458,6 @@ internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) _connHandler._federatedAuthenticationRequested = true; } - private void SSPIData(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength) - { - if (TdsParserStateObjectFactory.UseManagedSNI) - { - try - { - _physicalStateObj.GenerateSspiClientContext(receivedBuff, receivedLength, ref sendBuff, ref sendLength, _sniSpnBuffer); - } - catch (Exception e) - { - SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); - } - } - else - { - if (receivedBuff == null) - { - // if we do not have SSPI data coming from server, send over 0's for pointer and length - receivedLength = 0; - } - - // we need to respond to the server's message with SSPI data - if (0 != _physicalStateObj.GenerateSspiClientContext(receivedBuff, receivedLength, ref sendBuff, ref sendLength, _sniSpnBuffer)) - { - SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT); - } - } - } - - private void ProcessSSPI(int receivedLength) - { - SniContext outerContext = _physicalStateObj.SniContext; - _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; - // allocate received buffer based on length from SSPI message - byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength); - - // read SSPI data received from server - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - bool result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); - if (!result) - { - throw SQL.SynchronousCallMayNotPend(); - } - - // allocate send buffer and initialize length - byte[] rentedSendBuff = ArrayPool.Shared.Rent((int)s_maxSSPILength); - byte[] sendBuff = rentedSendBuff; - uint sendLength = s_maxSSPILength; - - // make call for SSPI data - SSPIData(receivedBuff, (uint)receivedLength, ref sendBuff, ref sendLength); - - // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! - _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0); - - ArrayPool.Shared.Return(rentedSendBuff, clearArray: true); - ArrayPool.Shared.Return(receivedBuff, clearArray: true); - - // set message type so server knows its a SSPI response - _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; - - // send to server - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.SniContext = outerContext; - } - - private void SSPIError(string error, string procedure) - { - Debug.Assert(!string.IsNullOrEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); - Debug.Assert(!string.IsNullOrEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); - - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, error, procedure, 0)); - ThrowExceptionAndWarning(_physicalStateObj); - } - internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) { // If this fails, the server will return a server error - Sameet Agarwal confirmed. @@ -9240,7 +9006,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout { // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand if ( - !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled + !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index b1bb4065c8..520367963d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -62,7 +62,7 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon AddError(parser.ProcessSNIError(this)); ThrowExceptionAndWarning(); } - + // we post a callback that represents the call to dispose; once the // object is disposed, the next callback will cause the GC Handle to // be released. @@ -75,6 +75,7 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon //////////////// internal abstract uint DisableSsl(); + internal abstract SSPIContextProvider CreateSSPIContextProvider(); internal abstract uint EnableMars(ref uint info); @@ -83,6 +84,8 @@ internal abstract uint Status get; } + internal abstract Guid? SessionId { get; } + internal abstract SessionHandle SessionHandle { get; @@ -236,8 +239,6 @@ internal abstract void CreatePhysicalSNIHandle( protected abstract void RemovePacketFromPendingList(PacketHandle pointer); - internal abstract uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer); - internal int DecrementPendingCallbacks(bool release) { int remaining = Interlocked.Decrement(ref _pendingCallbacks); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index 1e0141dd58..7d879ae4d5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -21,11 +21,7 @@ internal sealed class TdsParserStateObjectManaged : TdsParserStateObject { private SNIMarsConnection? _marsConnection; private SNIHandle? _sessionHandle; -#if NET7_0_OR_GREATER - private NegotiateAuthentication? _negotiateAuth = null; -#else - private SspiClientContextStatus? _sspiClientContextStatus; -#endif + public TdsParserStateObjectManaged(TdsParser parser) : base(parser) { } internal TdsParserStateObjectManaged(TdsParser parser, TdsParserStateObject physicalConnection, bool async) : @@ -232,6 +228,8 @@ internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint protected override PacketHandle EmptyReadPacket => PacketHandle.FromManagedPacket(null); + internal override Guid? SessionId => _sessionHandle?.ConnectionId; + internal override bool IsPacketEmpty(PacketHandle packet) => packet.ManagedPacket == null; internal override void ReleasePacket(PacketHandle syncReadPacket) @@ -389,30 +387,6 @@ internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) return TdsEnums.SNI_SUCCESS; } - internal override uint GenerateSspiClientContext(byte[] receivedBuff, - uint receivedLength, - ref byte[] sendBuff, - ref uint sendLength, - byte[][] _sniSpnBuffer) - { -#if NET7_0_OR_GREATER - _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = Encoding.Unicode.GetString(_sniSpnBuffer[0]) }); - sendBuff = _negotiateAuth.GetOutgoingBlob(receivedBuff, out NegotiateAuthenticationStatusCode statusCode)!; - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.GenerateSspiClientContext | Info | Session Id {0}, StatusCode={1}", _sessionHandle?.ConnectionId, statusCode); - if (statusCode is not NegotiateAuthenticationStatusCode.Completed and not NegotiateAuthenticationStatusCode.ContinueNeeded) - { - throw new InvalidOperationException(SQLMessage.SSPIGenerateError() + Environment.NewLine + statusCode); - } -#else - _sspiClientContextStatus ??= new SspiClientContextStatus(); - - SNIProxy.GenSspiClientContext(_sspiClientContextStatus, receivedBuff, ref sendBuff, _sniSpnBuffer); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.GenerateSspiClientContext | Info | Session Id {0}", _sessionHandle?.ConnectionId); -#endif - sendLength = (uint)(sendBuff != null ? sendBuff.Length : 0); - return 0; - } - internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) { protocolVersion = GetSessionSNIHandleHandleOrThrow().ProtocolVersion; @@ -432,5 +406,12 @@ private SNIHandle GetSessionSNIHandleHandleOrThrow() [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] // this forces the exception throwing code not to be inlined for performance private void ThrowClosedConnection() => throw ADP.ClosedConnectionError(); + + internal override SSPIContextProvider CreateSSPIContextProvider() +#if NET7_0_OR_GREATER + => new NegotiateSSPIContextProvider(); +#else + => new ManagedSSPIContextProvider(); +#endif } } 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 80fd68d5d8..6f26250072 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 @@ -167,7 +167,7 @@ internal override void CreatePhysicalSNIHandle( byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "Length of the provided SPN exceeded the buffer size."); spnBuffer[0] = srvSPN; - SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.",nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); + SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.", nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); } else { @@ -272,6 +272,8 @@ internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint protected override PacketHandle EmptyReadPacket => PacketHandle.FromNativePointer(default); + internal override Guid? SessionId => default; + internal override bool IsPacketEmpty(PacketHandle readPacket) { Debug.Assert(readPacket.Type == PacketHandle.NativePointerType || readPacket.Type == 0, "unexpected packet type when requiring NativePointer"); @@ -398,9 +400,6 @@ internal override uint EnableSsl(ref uint info, bool tlsFirst, string serverCert internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) => SNINativeMethodWrapper.SNISetInfo(Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); - internal override uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) - => SNINativeMethodWrapper.SNISecGenClientContext(Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer[0]); - internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) { uint returnValue = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining(), out uint nativeProtocolVersion); @@ -452,6 +451,8 @@ internal override void DisposePacketCache() } } + internal override SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); + internal sealed class WritePacketCache : IDisposable { private bool _disposed; 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 4dc1ae6393..2dd8d69b08 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -153,6 +153,21 @@ Microsoft\Data\ProviderBase\TimeoutTimer.cs + + Microsoft\Data\SqlClient\SSPI\ManagedSSPIContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\SSPIContextProvider.cs + + + Microsoft\Data\SqlClient\TdsParser.cs + Microsoft\Data\Sql\SqlDataSourceEnumerator.cs @@ -751,4 +766,4 @@ - + \ No newline at end of file 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 7a9bbfdfd3..6d49a79051 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 @@ -28,14 +28,15 @@ namespace Microsoft.Data.SqlClient { - // The TdsParser Object controls reading/writing to the netlib, parsing the tds, // and surfacing objects to the user. - sealed internal class TdsParser + sealed internal partial class TdsParser { private static int _objectTypeCount; // EventSource Counter private readonly SqlClientLogger _logger = new SqlClientLogger(); + private SSPIContextProvider _authenticationProvider; + internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); static Task completedTask; @@ -236,16 +237,9 @@ internal static void Assert(string message) // NIC address caching private static byte[] s_nicAddress; // cache the NIC address from the registry - // SSPI variables - private static bool s_fSSPILoaded = false; // bool to indicate whether library has been loaded - - private volatile static UInt32 s_maxSSPILength = 0; // variable to hold max SSPI data size, keep for token from server - // textptr sequence private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - private static object s_tdsParserLock = new object(); - // Various other statics private const int ATTENTION_TIMEOUT = 5000; // internal attention timeout, in ticks @@ -549,7 +543,8 @@ internal void Connect(ServerInfo serverInfo, // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - LoadSSPILibrary(); + _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); + if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) { // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. @@ -567,7 +562,9 @@ internal void Connect(ServerInfo serverInfo, } else { + _authenticationProvider = null; _sniSpnBuffer = null; + switch (authType) { case SqlAuthenticationMethod.ActiveDirectoryPassword: @@ -655,6 +652,8 @@ internal void Connect(ServerInfo serverInfo, FQDNforDNSCache, hostNameInCertificate); + _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); + if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -760,6 +759,8 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ResolvedServerName, hostNameInCertificate); + _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); + if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -2547,7 +2548,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) { // Fire SqlInfoMessage here - FireInfoMessageEvent(connection,cmdHandler, stateObj, error); + FireInfoMessageEvent(connection, cmdHandler, stateObj, error); } else { @@ -8904,205 +8905,6 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } - internal void TdsLogin(SqlLogin rec, - TdsEnums.FeatureExtension requestedFeatures, - SessionData recoverySessionData, - FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, - SqlClientOriginalNetworkAddressInfo originalNetworkAddressInfo, - SqlConnectionEncryptOption encrypt) - { - _physicalStateObj.SetTimeoutSeconds(rec.timeout); - - Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); - Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); - - Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option."); - Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); - Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); - Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); - - Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); - - Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value"); - - Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password"); - Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password"); - Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value"); - - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth); - - // Add CTAIP Provider - // - if (originalNetworkAddressInfo != null) - { - SNINativeMethodWrapper.CTAIPProviderInfo cauthInfo = new SNINativeMethodWrapper.CTAIPProviderInfo(); - cauthInfo.originalNetworkAddress = originalNetworkAddressInfo.Address.GetAddressBytes(); - cauthInfo.fromDataSecurityProxy = originalNetworkAddressInfo.IsFromDataSecurityProxy; - - UInt32 error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.CTAIP_PROV, cauthInfo); - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - try - { } // EmptyTry/Finally to avoid FXCop violation - finally - { - _physicalStateObj.ClearAllWritePackets(); - } - } - - // get the password up front to use in sspi logic below - byte[] encryptedPassword = null; - byte[] encryptedChangePassword = null; - int encryptedPasswordLengthInBytes; - int encryptedChangePasswordLengthInBytes; - bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None); - - string userName; - - if (rec.credential != null) - { - userName = rec.credential.UserId; - encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2; - } - else - { - userName = rec.userName; - encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password); - encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte - } - - if (rec.newSecurePassword != null) - { - encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2; - } - else - { - encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword); - encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length; - } - - - // set the message type - _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7; - - // length in bytes - int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN; - - string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; - Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec"); - - // add up variable-len portions (multiply by 2 for byte len of char strings) - // - checked - { - length += (rec.hostName.Length + rec.applicationName.Length + - rec.serverName.Length + clientInterfaceName.Length + - rec.language.Length + rec.database.Length + - rec.attachDBFilename.Length) * 2; - if (useFeatureExt) - { - length += 4; - } - } - - // allocate memory for SSPI variables - byte[] outSSPIBuff = null; - UInt32 outSSPILength = 0; - - // only add lengths of password and username if not using SSPI or requesting federated authentication info - if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) - { - checked - { - length += (userName.Length * 2) + encryptedPasswordLengthInBytes - + encryptedChangePasswordLengthInBytes; - } - } - else - { - if (rec.useSSPI) - { - // now allocate proper length of buffer, and set length - outSSPIBuff = new byte[s_maxSSPILength]; - outSSPILength = s_maxSSPILength; - - // Call helper function for SSPI data and actual length. - // Since we don't have SSPI data from the server, send null for the - // byte[] buffer and 0 for the int length. - Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); - _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; - SSPIData(null, 0, outSSPIBuff, ref outSSPILength); - if (outSSPILength > int.MaxValue) - { - throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 - } - _physicalStateObj.SniContext = SniContext.Snix_Login; - - checked - { - length += (int)outSSPILength; - } - } - } - - int feOffset = length; - // calculate and reserve the required bytes for the featureEx - length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); - - WriteLoginData(rec, - requestedFeatures, - recoverySessionData, - fedAuthFeatureExtensionData, - encrypt, - encryptedPassword, - encryptedChangePassword, - encryptedPasswordLengthInBytes, - encryptedChangePasswordLengthInBytes, - useFeatureExt, - userName, - length, - feOffset, - clientInterfaceName, - outSSPIBuff, - outSSPILength); - - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information - _physicalStateObj.HasPendingData = true; - _physicalStateObj._messageStatus = 0; - - // Remvove CTAIP Provider after login record is sent. - // - if (originalNetworkAddressInfo != null) - { - UInt32 error = SNINativeMethodWrapper.SNIRemoveProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.CTAIP_PROV); - if (error != TdsEnums.SNI_SUCCESS) - { - _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - try - { } // EmptyTry/Finally to avoid FXCop violation - finally - { - _physicalStateObj.ClearAllWritePackets(); - } - } - }// tdsLogin - private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -9472,94 +9274,6 @@ internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) _connHandler._federatedAuthenticationRequested = true; } - private void SSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength) - { - SNISSPIData(receivedBuff, receivedLength, sendBuff, ref sendLength); - } - - private void SNISSPIData(byte[] receivedBuff, UInt32 receivedLength, byte[] sendBuff, ref UInt32 sendLength) - { - if (receivedBuff == null) - { - // we do not have SSPI data coming from server, so send over 0's for pointer and length - receivedLength = 0; - } - // we need to respond to the server's message with SSPI data - if (0 != SNINativeMethodWrapper.SNISecGenClientContext(_physicalStateObj.Handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer)) - { - SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT); - } - } - - - private void ProcessSSPI(int receivedLength) - { - SniContext outerContext = _physicalStateObj.SniContext; - _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; - // allocate received buffer based on length from SSPI message - byte[] receivedBuff = new byte[receivedLength]; - - // read SSPI data received from server - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - bool result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); - if (!result) - { throw SQL.SynchronousCallMayNotPend(); } - - // allocate send buffer and initialize length - byte[] sendBuff = new byte[s_maxSSPILength]; - UInt32 sendLength = s_maxSSPILength; - - // make call for SSPI data - SSPIData(receivedBuff, (UInt32)receivedLength, sendBuff, ref sendLength); - - // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! - _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0); - - // set message type so server knows its a SSPI response - _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; - - // send to server - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.SniContext = outerContext; - } - - private void SSPIError(string error, string procedure) - { - Debug.Assert(!ADP.IsEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); - Debug.Assert(!ADP.IsEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); - - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _server, error, procedure, 0)); - ThrowExceptionAndWarning(_physicalStateObj); - } - - private void LoadSSPILibrary() - { - // Outer check so we don't acquire lock once once it's loaded. - if (!s_fSSPILoaded) - { - lock (s_tdsParserLock) - { - // re-check inside lock - if (!s_fSSPILoaded) - { - // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded. - UInt32 maxLength = 0; - if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength)) - SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE); - - s_maxSSPILength = maxLength; - s_fSSPILoaded = true; - - } - } - } - - if (s_maxSSPILength > Int32.MaxValue) - { - throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 - } - } - internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) { // If this fails, the server will return a server error - Sameet Agarwal confirmed. @@ -10123,7 +9837,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout { // Throw an exception if ForceColumnEncryption is set on a parameter and the ColumnEncryption is not enabled on SqlConnection or SqlCommand if ( - !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled + !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled)) ) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 5d4a332665..9ce3c4ec11 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -100,6 +100,8 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; } + internal SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); + //////////////// // Properties // //////////////// @@ -110,7 +112,7 @@ internal SNIHandle Handle return _sessionHandle; } } - + internal uint Status { get diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/ManagedSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/ManagedSSPIContextProvider.cs new file mode 100644 index 0000000000..25fb2cadae --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/ManagedSSPIContextProvider.cs @@ -0,0 +1,23 @@ +#if !NETFRAMEWORK && !NET7_0_OR_GREATER + +using Microsoft.Data.SqlClient.SNI; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class ManagedSSPIContextProvider : SSPIContextProvider + { + private SspiClientContextStatus? _sspiClientContextStatus; + + internal override void GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) + { + _sspiClientContextStatus ??= new SspiClientContextStatus(); + + SNIProxy.GenSspiClientContext(_sspiClientContextStatus, receivedBuff, ref sendBuff, _sniSpnBuffer); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.GenerateSspiClientContext | Info | Session Id {0}", _physicalStateObj.SessionId); + sendLength = (uint)(sendBuff != null ? sendBuff.Length : 0); + } + } +} +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs new file mode 100644 index 0000000000..718608d136 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class NativeSSPIContextProvider : SSPIContextProvider + { + private static readonly object s_tdsParserLock = new(); + + // bool to indicate whether library has been loaded + private static bool s_fSSPILoaded; + + // variable to hold max SSPI data size, keep for token from server + private volatile static uint s_maxSSPILength; + + internal override uint MaxSSPILength => s_maxSSPILength; + + private protected override void Initialize() + { + LoadSSPILibrary(); + } + + private void LoadSSPILibrary() + { + // Outer check so we don't acquire lock once it's loaded. + if (!s_fSSPILoaded) + { + lock (s_tdsParserLock) + { + // re-check inside lock + if (!s_fSSPILoaded) + { + // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded. + uint maxLength = 0; + + if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength)) + SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE); + + s_maxSSPILength = maxLength; + s_fSSPILoaded = true; + } + } + } + + if (s_maxSSPILength > int.MaxValue) + { + throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 + } + } + + internal override void GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) + { +#if NETFRAMEWORK + SNIHandle handle = _physicalStateObj.Handle; +#else + Debug.Assert(_physicalStateObj.SessionHandle.Type == SessionHandle.NativeHandleType); + SNIHandle handle = _physicalStateObj.SessionHandle.NativeHandle; +#endif + if (0 != SNINativeMethodWrapper.SNISecGenClientContext(handle, receivedBuff, receivedLength, sendBuff, ref sendLength, _sniSpnBuffer[0])) + { + throw new InvalidOperationException(SQLMessage.SSPIGenerateError()); + } + } + } +} + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs new file mode 100644 index 0000000000..bc11cf29c9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs @@ -0,0 +1,28 @@ +#if NET7_0_OR_GREATER + +using System; +using System.Text; +using System.Net.Security; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class NegotiateSSPIContextProvider : SSPIContextProvider + { + private NegotiateAuthentication? _negotiateAuth = null; + + internal override void GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) + { + _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = Encoding.Unicode.GetString(_sniSpnBuffer[0]) }); + sendBuff = _negotiateAuth.GetOutgoingBlob(receivedBuff, out NegotiateAuthenticationStatusCode statusCode)!; + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.GenerateSspiClientContext | Info | Session Id {0}, StatusCode={1}", _physicalStateObj.SessionId, statusCode); + if (statusCode is not NegotiateAuthenticationStatusCode.Completed and not NegotiateAuthenticationStatusCode.ContinueNeeded) + { + throw new InvalidOperationException(SQLMessage.SSPIGenerateError() + Environment.NewLine + statusCode); + } + sendLength = (uint)(sendBuff != null ? sendBuff.Length : 0); + } + } +} +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs new file mode 100644 index 0000000000..fa8c27e3ab --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using Microsoft.Data.Common; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal abstract class SSPIContextProvider + { + private TdsParser _parser = null!; + private ServerInfo _serverInfo = null!; + private protected TdsParserStateObject _physicalStateObj = null!; + + internal virtual uint MaxSSPILength => 4096; // TODO: what is a good default here? + + internal void Initialize(ServerInfo serverInfo, TdsParserStateObject physicalStateObj, TdsParser parser) + { + _parser = parser; + _physicalStateObj = physicalStateObj; + _serverInfo = serverInfo; + + Initialize(); + } + + private protected virtual void Initialize() + { + } + + internal abstract void GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer); + + internal void SSPIData(byte[] receivedBuff, UInt32 receivedLength, ref byte[] sendBuff, ref UInt32 sendLength, byte[] sniSpnBuffer) + => SSPIData(receivedBuff, receivedLength, ref sendBuff, ref sendLength, new[] { sniSpnBuffer }); + + internal void SSPIData(byte[] receivedBuff, UInt32 receivedLength, ref byte[] sendBuff, ref UInt32 sendLength, byte[][] sniSpnBuffer) + { + try + { + GenerateSspiClientContext(receivedBuff, receivedLength, ref sendBuff, ref sendLength, sniSpnBuffer); + } + catch (Exception e) + { + SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); + } + } + + protected void SSPIError(string error, string procedure) + { + Debug.Assert(!ADP.IsEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); + Debug.Assert(!ADP.IsEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); + + _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _serverInfo.ResolvedServerName, error, procedure, 0)); + _parser.ThrowExceptionAndWarning(_physicalStateObj); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs index 28818a16f1..953a5d5e55 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ExtendedClrTypeCode.cs @@ -49,6 +49,10 @@ internal enum ExtendedClrTypeCode IEnumerableOfSqlDataRecord, // System.Collections.Generic.IEnumerable TimeSpan, // System.TimeSpan DateTimeOffset, // System.DateTimeOffset +#if NET6_0_OR_GREATER + DateOnly, // System.DateOnly + TimeOnly, // System.TimeOnly +#endif Stream, // System.IO.Stream TextReader, // System.IO.TextReader XmlReader, // System.Xml.XmlReader diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs index 590429222a..d3c01546ce 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs @@ -75,6 +75,10 @@ internal class MetaDataUtilsSmi SqlDbType.Structured, // System.Collections.Generic.IEnumerable SqlDbType.Time, // System.TimeSpan SqlDbType.DateTimeOffset, // System.DateTimeOffset +#if NET6_0_OR_GREATER + SqlDbType.Date, // System.DateOnly + SqlDbType.Time, // System.TimeOnly +#endif }; @@ -86,7 +90,11 @@ internal class MetaDataUtilsSmi private static Dictionary CreateTypeToExtendedTypeCodeMap() { +#if NET6_0_OR_GREATER + int Count = 44; +#else int Count = 42; +#endif // Keep this initialization list in the same order as ExtendedClrTypeCode for ease in validating! var dictionary = new Dictionary(Count) { @@ -132,6 +140,10 @@ private static Dictionary CreateTypeToExtendedTypeCod { typeof(IEnumerable), ExtendedClrTypeCode.IEnumerableOfSqlDataRecord }, { typeof(TimeSpan), ExtendedClrTypeCode.TimeSpan }, { typeof(DateTimeOffset), ExtendedClrTypeCode.DateTimeOffset }, +#if NET6_0_OR_GREATER + { typeof(DateOnly), ExtendedClrTypeCode.DateOnly }, + { typeof(TimeOnly), ExtendedClrTypeCode.TimeOnly }, +#endif }; return dictionary; } @@ -244,6 +256,16 @@ Type udtType extendedCode = ExtendedClrTypeCode.Char; break; case SqlDbType.Date: +#if NET6_0_OR_GREATER + if (value.GetType() == typeof(DateOnly)) + extendedCode = ExtendedClrTypeCode.DateOnly; + else if (value.GetType() == typeof(DateTime)) + extendedCode = ExtendedClrTypeCode.DateTime; + else if (value.GetType() == typeof(SqlDateTime)) + extendedCode = ExtendedClrTypeCode.SqlDateTime; + + break; +#endif case SqlDbType.DateTime2: #if NETFRAMEWORK if (smiVersion >= SmiContextFactory.Sql2008Version) @@ -330,6 +352,14 @@ Type udtType extendedCode = ExtendedClrTypeCode.Invalid; } break; +#if NET6_0_OR_GREATER + case SqlDbType.Time: + if (value.GetType() == typeof(TimeOnly)) + extendedCode = ExtendedClrTypeCode.TimeOnly; + else if (value.GetType() == typeof(TimeSpan)) + extendedCode = ExtendedClrTypeCode.TimeSpan; + break; +#else case SqlDbType.Time: if (value.GetType() == typeof(TimeSpan) #if NETFRAMEWORK @@ -338,6 +368,7 @@ Type udtType ) extendedCode = ExtendedClrTypeCode.TimeSpan; break; +#endif case SqlDbType.DateTimeOffset: if (value.GetType() == typeof(DateTimeOffset) #if NETFRAMEWORK @@ -500,11 +531,7 @@ internal static SmiExtendedMetaData SqlMetaDataToSmiExtendedMetaData(SqlMetaData source.Scale, source.LocaleId, source.CompareOptions, -#if NETFRAMEWORK source.Type, -#else - null, -#endif source.Name, typeSpecificNamePart1, typeSpecificNamePart2, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs index 61bcec417f..4662bc6012 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs @@ -1542,6 +1542,14 @@ value is DataFeed SetCompatibleValue(sink, setters, ordinal, metaData, charsValue, ExtendedClrTypeCode.CharArray, 0); break; } +#if NET6_0_OR_GREATER + case ExtendedClrTypeCode.DateOnly: + SetDateTime_Checked(sink, setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); + break; + case ExtendedClrTypeCode.TimeOnly: + SetTimeSpan_Checked(sink, (SmiTypedGetterSetter)setters, ordinal, metaData, ((TimeOnly)value).ToTimeSpan()); + break; +#endif case ExtendedClrTypeCode.DateTime: SetDateTime_Checked(sink, setters, ordinal, metaData, (DateTime)value); break; @@ -2899,6 +2907,10 @@ int length /*EnSDR*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X, _ , _ , _ , _ , },/*IEnurerable*/ /*TmSpn*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeSpan*/ /*DTOst*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , X , },/*DateTimeOffset*/ +#if NET6_0_OR_GREATER +/*DOnly*/{ _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, X , _ , X , _ , },/*DateOnly*/ +/*TOnly*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeOnly*/ +#endif /*Strm */{ _ , X , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , X , _, _ , _ , _ , _ , },/*Stream*/ /*TxRdr*/{ _ , _ , _ , X , _ , _ , _ , _ , _ , _ , X , X , X , _ , _ , _ , _ , _ , X , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*TextReader*/ /*XmlRd*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*XmlReader*/ @@ -2951,6 +2963,10 @@ int length /*EnSDR*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X, _ , _ , _ , _ , },/*IEnurerable*/ /*TmSpn*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeSpan*/ /*DTOst*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , X , },/*DateTimeOffset*/ +#if NET6_0_OR_GREATER +/*DOnly*/{ _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _, X , _ , X , _ , },/*DateOnly*/ +/*TOnly*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , X , _ , _ , },/*TimeOnly*/ +#endif /*Strm */{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*Stream*/ /*TxRdr*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*TextReader*/ /*XmlRd*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _, _ , _ , _ , _ , },/*XmlReader*/ diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs index 9cfc077da4..31678a3107 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs @@ -811,7 +811,7 @@ internal SqlGuid SqlGuid { if (StorageType.Guid == _type) { - return new SqlGuid(_value._guid); + return IsNull ? SqlGuid.Null : new SqlGuid(_value._guid); } else if (StorageType.SqlGuid == _type) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs index 9ea820000e..a563d0dfc6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Buffers.Binary; using System.Diagnostics; using System.Text; @@ -18,7 +19,8 @@ sealed internal class SqlSequentialTextReader : System.IO.TextReader private readonly int _columnIndex; // The index of out column in the table private readonly Encoding _encoding; // Encoding for this character stream private readonly Decoder _decoder; // Decoder based on the encoding (NOTE: Decoders are stateful as they are designed to process streams of data) - private byte[] _leftOverBytes; // Bytes leftover from the last Read() operation - this can be null if there were no bytes leftover (Possible optimization: re-use the same array?) + private byte[] _leftOverBytes; // Bytes leftover from the last Read() operation - this can be null if there were no bytes leftover + private int _leftOverByteBufferUsed; //Number of bytes used from _leftOverBytes buffer - will be zero in case of null buffer private int _peekedChar; // The last character that we peeked at (or -1 if we haven't peeked at anything) private Task _currentTask; // The current async task private readonly CancellationTokenSource _disposalTokenSource; // Used to indicate that a cancellation is requested due to disposal @@ -357,23 +359,18 @@ private byte[] PrepareByteBuffer(int numberOfChars, out int byteBufferUsed) if (_leftOverBytes != null) { - // If we have more leftover bytes than we need for this conversion, then just re-use the leftover buffer - if (_leftOverBytes.Length > byteBufferSize) - { - byteBuffer = _leftOverBytes; - byteBufferUsed = byteBuffer.Length; - } - else - { - // Otherwise, copy over the leftover buffer - byteBuffer = new byte[byteBufferSize]; - Buffer.BlockCopy(_leftOverBytes, 0, byteBuffer, 0, _leftOverBytes.Length); - byteBufferUsed = _leftOverBytes.Length; - } + // Copy over the leftover buffer + byteBuffer = ArrayPool.Shared.Rent(byteBufferSize); + Buffer.BlockCopy(_leftOverBytes, 0, byteBuffer, 0, _leftOverByteBufferUsed); + byteBufferUsed = _leftOverByteBufferUsed; + //return _leftOverBytes and clean _leftOverBytes reference + ArrayPool.Shared.Return(_leftOverBytes); + _leftOverBytes = null; + _leftOverByteBufferUsed = 0; } else { - byteBuffer = new byte[byteBufferSize]; + byteBuffer = ArrayPool.Shared.Rent(byteBufferSize); byteBufferUsed = 0; } } @@ -402,14 +399,26 @@ private int DecodeBytesToChars(byte[] inBuffer, int inBufferCount, char[] outBuf // completed may be false and there is no spare bytes if the Decoder has stored bytes to use later if ((!completed) && (bytesUsed < inBufferCount)) { - _leftOverBytes = new byte[inBufferCount - bytesUsed]; - Buffer.BlockCopy(inBuffer, bytesUsed, _leftOverBytes, 0, _leftOverBytes.Length); + _leftOverByteBufferUsed = inBufferCount - bytesUsed; + _leftOverBytes = ArrayPool.Shared.Rent(_leftOverByteBufferUsed); + + Buffer.BlockCopy(inBuffer, bytesUsed, _leftOverBytes, 0, _leftOverByteBufferUsed); } else { // If Convert() sets completed to true, then it must have used all of the bytes we gave it Debug.Assert(bytesUsed >= inBufferCount, "Converted completed, but not all bytes were used"); - _leftOverBytes = null; + if (_leftOverBytes != null) + { + ArrayPool.Shared.Return(_leftOverBytes); + _leftOverBytes = null; + _leftOverByteBufferUsed = 0; + } + } + + if (inBuffer.Length > 0) + { + ArrayPool.Shared.Return(inBuffer); } Debug.Assert(((_reader == null) || (_reader.ColumnDataBytesRemaining() > 0) || (!completed) || (_leftOverBytes == null)), "Stream has run out of data and the decoder finished, but there are leftover bytes"); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs new file mode 100644 index 0000000000..4366b4665a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -0,0 +1,265 @@ +using System; +using System.Buffers; +using System.Diagnostics; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal partial class TdsParser + { + internal void ProcessSSPI(int receivedLength) + { + Debug.Assert(_authenticationProvider is not null); + + SniContext outerContext = _physicalStateObj.SniContext; + _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; + // allocate received buffer based on length from SSPI message + byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength); + + // read SSPI data received from server + Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); + bool result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); + if (!result) + { + throw SQL.SynchronousCallMayNotPend(); + } + + // allocate send buffer and initialize length + byte[] rentedSendBuff = ArrayPool.Shared.Rent((int)_authenticationProvider!.MaxSSPILength); + byte[] sendBuff = rentedSendBuff; // need to track these separately in case someone updates the ref parameter + uint sendLength = _authenticationProvider.MaxSSPILength; + + // make call for SSPI data + _authenticationProvider.SSPIData(receivedBuff, (uint)receivedLength, ref sendBuff, ref sendLength, _sniSpnBuffer); + + // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! + _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0); + + ArrayPool.Shared.Return(rentedSendBuff, clearArray: true); + ArrayPool.Shared.Return(receivedBuff, clearArray: true); + + // set message type so server knows its a SSPI response + _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; + + // send to server + _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); + _physicalStateObj.SniContext = outerContext; + } + +#nullable disable + + internal void TdsLogin( + SqlLogin rec, + TdsEnums.FeatureExtension requestedFeatures, + SessionData recoverySessionData, + FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, +#if NETFRAMEWORK + SqlClientOriginalNetworkAddressInfo originalNetworkAddressInfo, +#endif + SqlConnectionEncryptOption encrypt) + { + _physicalStateObj.SetTimeoutSeconds(rec.timeout); + + Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); + Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); + + Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option."); + Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); + Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); + Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); + + Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); + Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); + + Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value"); + Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value"); + + Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password"); + Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password"); + Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value"); + + Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); + _connHandler!.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); + _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth); + +#if NETFRAMEWORK + // Add CTAIP Provider + // + if (originalNetworkAddressInfo != null) + { + SNINativeMethodWrapper.CTAIPProviderInfo cauthInfo = new SNINativeMethodWrapper.CTAIPProviderInfo(); + cauthInfo.originalNetworkAddress = originalNetworkAddressInfo.Address.GetAddressBytes(); + cauthInfo.fromDataSecurityProxy = originalNetworkAddressInfo.IsFromDataSecurityProxy; + + UInt32 error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.CTAIP_PROV, cauthInfo); + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + try + { } // EmptyTry/Finally to avoid FXCop violation + finally + { + _physicalStateObj.ClearAllWritePackets(); + } + } +#endif + + // get the password up front to use in sspi logic below + byte[] encryptedPassword = null; + byte[] encryptedChangePassword = null; + int encryptedPasswordLengthInBytes; + int encryptedChangePasswordLengthInBytes; + bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None); + + string userName; + + if (rec.credential != null) + { + userName = rec.credential.UserId; + encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2; + } + else + { + userName = rec.userName; + encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password); + encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte + } + + if (rec.newSecurePassword != null) + { + encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2; + } + else + { + encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword); + encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length; + } + + // set the message type + _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7; + + // length in bytes + int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN; + + string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; + Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec"); + + // add up variable-len portions (multiply by 2 for byte len of char strings) + // + checked + { + length += (rec.hostName.Length + rec.applicationName.Length + + rec.serverName.Length + clientInterfaceName.Length + + rec.language.Length + rec.database.Length + + rec.attachDBFilename.Length) * 2; + if (useFeatureExt) + { + length += 4; + } + } + + // allocate memory for SSPI variables + byte[] rentedSSPIBuff = null; + byte[] outSSPIBuff = null; // track the rented buffer as a separate variable in case it is updated via the ref parameter + uint outSSPILength = 0; + + // only add lengths of password and username if not using SSPI or requesting federated authentication info + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) + { + checked + { + length += (userName.Length * 2) + encryptedPasswordLengthInBytes + + encryptedChangePasswordLengthInBytes; + } + } + else + { + if (rec.useSSPI) + { + // now allocate proper length of buffer, and set length + outSSPILength = _authenticationProvider.MaxSSPILength; + rentedSSPIBuff = ArrayPool.Shared.Rent((int)outSSPILength); + outSSPIBuff = rentedSSPIBuff; + + // Call helper function for SSPI data and actual length. + // Since we don't have SSPI data from the server, send null for the + // byte[] buffer and 0 for the int length. + Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); + _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; + _authenticationProvider.SSPIData(Array.Empty(), 0, ref outSSPIBuff, ref outSSPILength, _sniSpnBuffer); + + if (outSSPILength > int.MaxValue) + { + throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 + } + _physicalStateObj.SniContext = SniContext.Snix_Login; + + checked + { + length += (int)outSSPILength; + } + } + } + + int feOffset = length; + // calculate and reserve the required bytes for the featureEx + length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); + + WriteLoginData(rec, + requestedFeatures, + recoverySessionData, + fedAuthFeatureExtensionData, + encrypt, + encryptedPassword, + encryptedChangePassword, + encryptedPasswordLengthInBytes, + encryptedChangePasswordLengthInBytes, + useFeatureExt, + userName, + length, + feOffset, + clientInterfaceName, + outSSPIBuff, + outSSPILength); + + if (rentedSSPIBuff != null) + { + ArrayPool.Shared.Return(rentedSSPIBuff, clearArray: true); + } + + _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); + _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information + _physicalStateObj.HasPendingData = true; + _physicalStateObj._messageStatus = 0; + +#if NETFRAMEWORK + // Remvove CTAIP Provider after login record is sent. + // + if (originalNetworkAddressInfo != null) + { + UInt32 error = SNINativeMethodWrapper.SNIRemoveProvider(_physicalStateObj.Handle, SNINativeMethodWrapper.ProviderEnum.CTAIP_PROV); + if (error != TdsEnums.SNI_SUCCESS) + { + _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); + ThrowExceptionAndWarning(_physicalStateObj); + } + + try + { } // EmptyTry/Finally to avoid FXCop violation + finally + { + _physicalStateObj.ClearAllWritePackets(); + } + } +#endif + }// tdsLogin + } +} 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 8f41187ed6..c60dd01c72 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -257,8 +257,10 @@ private bool VerifyHealthReportAgainstRootCertificate(X509Certificate2Collection // 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)) { 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 d6b8a8565c..aedae160a7 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 @@ -33,6 +33,7 @@ + @@ -95,6 +96,8 @@ + + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs new file mode 100644 index 0000000000..6aef4b60b2 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs @@ -0,0 +1,253 @@ +// 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.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public sealed class SqlBufferTests + { + static SqlBufferTests() + { + const string sqlBufferTypeFullName = "Microsoft.Data.SqlClient.SqlBuffer"; + const string storageTypeName = nameof(SqlBufferProxy.StorageType); + + var assembly = typeof(SqlClientFactory).Assembly; + _sqlBufferType = assembly.GetType(sqlBufferTypeFullName) + ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}]"); + _storageTypeType = _sqlBufferType.GetNestedTypes(BindingFlags.NonPublic) + .FirstOrDefault(x => x.Name == storageTypeName) + ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}+{storageTypeName}]"); + } + + private static readonly Type _sqlBufferType; + private static readonly Type _storageTypeType; + private readonly SqlBufferProxy _target = new(); + + public static IEnumerable GetStorageTypeValues() + { +#if NET6_0_OR_GREATER + return Enum.GetValues() + .Select(x => new object[] { x }); +#else + return Enum.GetValues(typeof(SqlBufferProxy.StorageType)) + .OfType() + .Select(x => new object[] { x }); +#endif + } + + [Theory] + [MemberData(nameof(GetStorageTypeValues))] + public void StorageTypeInProxyShouldHaveTheSameValueAsOriginal(SqlBufferProxy.StorageType expected) + { + var originalEnumName = Enum.GetName(_storageTypeType, (int)expected); + + Assert.Equal(expected.ToString(), originalEnumName); + } + + [Fact] + public void GuidShouldThrowWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Throws(() => _target.Guid); + } + + [Theory] + [InlineData(SqlBufferProxy.StorageType.Guid)] + [InlineData(SqlBufferProxy.StorageType.SqlGuid)] + public void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + { + _target.SetToNullOfType(storageType); + + Assert.Throws(() => _target.Guid); + } + + [Fact] + public void GuidShouldReturnWhenGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.Guid = expected; + + Assert.Equal(expected, _target.Guid); + } + + [Fact] + public void GuidShouldReturnExpectedWhenSqlGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.Guid); + } + + [Theory] + [InlineData(SqlBufferProxy.StorageType.Guid)] + [InlineData(SqlBufferProxy.StorageType.SqlGuid)] + public void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + { + _target.SetToNullOfType(storageType); + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlGuid); + } + + [Fact] + public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlGuid); + } + + [Fact] + public void SqlValueShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlValue); + } + + [Fact] + public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlValue); + } + + public sealed class SqlBufferProxy + { + public enum StorageType + { + Empty = 0, + Boolean, + Byte, + DateTime, + Decimal, + Double, + Int16, + Int32, + Int64, + Guid, + Money, + Single, + String, + SqlBinary, + SqlCachedBuffer, + SqlGuid, + SqlXml, + Date, + DateTime2, + DateTimeOffset, + Time, + } + + private static readonly PropertyInfo _guidProperty; + private static readonly PropertyInfo _sqlGuidProperty; + private static readonly PropertyInfo _sqlValueProperty; + private static readonly MethodInfo _setToNullOfTypeMethod; + private readonly object _instance; + + static SqlBufferProxy() + { + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + _guidProperty = _sqlBufferType.GetProperty(nameof(Guid), flags); + _sqlGuidProperty = _sqlBufferType.GetProperty(nameof(SqlGuid), flags); + _sqlValueProperty = _sqlBufferType.GetProperty(nameof(SqlValue), flags); + _setToNullOfTypeMethod = _sqlBufferType.GetMethod(nameof(SetToNullOfType), flags); + } + + public SqlBufferProxy() + { + _instance = Activator.CreateInstance(_sqlBufferType, true); + } + + public Guid Guid + { + get => GetPropertyValue(_guidProperty); + set => SetPropertyValue(_guidProperty, value); + } + + public SqlGuid SqlGuid + { + get => GetPropertyValue(_sqlGuidProperty); + set => SetPropertyValue(_sqlGuidProperty, value); + } + + public object SqlValue + { + get => GetPropertyValue(_sqlValueProperty); + } + + public void SetToNullOfType(StorageType storageType) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + _setToNullOfTypeMethod + .Invoke(_instance, BindingFlags.DoNotWrapExceptions, null, new object[] { (int)storageType }, null); +#else + _setToNullOfTypeMethod.Invoke(_instance, new object[] { (int)storageType }); +#endif + } + + private T GetPropertyValue(PropertyInfo property) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return (T)property.GetValue(_instance, BindingFlags.DoNotWrapExceptions, null, null, null); +#else + try + { + return (T)property.GetValue(_instance); + } + catch (TargetInvocationException e) + { + throw e.InnerException!; + } +#endif + } + + private void SetPropertyValue(PropertyInfo property, object value) + { +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + property.SetValue(_instance, value, BindingFlags.DoNotWrapExceptions, null, null, null); +#else + try + { + property.SetValue(_instance, value); + } + catch (TargetInvocationException e) + { + throw e.InnerException!; + } +#endif + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs index 87f6c167ec..0e748c5d74 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs @@ -8,6 +8,7 @@ using System.Data; using System.Data.SqlTypes; using Microsoft.Data.SqlClient.Server; +using Microsoft.SqlServer.Types; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -318,6 +319,19 @@ public void GetChar_ThrowsNotSupported() Assert.Throws(() => record.GetChar(0)); } + [Theory] + [ClassData(typeof(GetUdtTypeTestData))] + public void GetUdt_ReturnsValue(Type udtType, object value, string serverTypeName) + { + SqlMetaData[] metadata = new SqlMetaData[] { new SqlMetaData(nameof(udtType.Name), SqlDbType.Udt, udtType, serverTypeName) }; + + SqlDataRecord record = new SqlDataRecord(metadata); + + record.SetValue(0, value); + + Assert.Equal(value.ToString(), record.GetValue(0).ToString()); + } + [Theory] [ClassData(typeof(GetXXXBadTypeTestData))] public void GetXXX_ThrowsIfBadType(Func getXXX) @@ -342,8 +356,8 @@ public void GetXXX_ReturnValue(SqlDbType dbType, object value, Func + { + public IEnumerator GetEnumerator() + { + yield return new object[] { typeof(SqlGeography), SqlGeography.Point(43, -81, 4326), "Geography" }; + yield return new object[] { typeof(SqlGeometry), SqlGeometry.Point(43, -81, 4326), "Geometry" }; + yield return new object[] { typeof(SqlHierarchyId), SqlHierarchyId.Parse("/"), "HierarchyId" }; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + public class GetXXXCheckValueTestData : IEnumerable { public IEnumerator GetEnumerator() @@ -383,6 +412,10 @@ public IEnumerator GetEnumerator() yield return new object[] { SqlDbType.DateTime, DateTime.Now, new Func(r => r.GetDateTime(0)) }; yield return new object[] { SqlDbType.DateTimeOffset, new DateTimeOffset(DateTime.Now), new Func(r => r.GetDateTimeOffset(0)) }; yield return new object[] { SqlDbType.Time, TimeSpan.FromHours(1), new Func(r => r.GetTimeSpan(0)) }; + yield return new object[] { SqlDbType.Date, DateTime.Now.Date, new Func(r => r.GetDateTime(0)) }; + yield return new object[] { SqlDbType.Bit, bool.Parse(bool.TrueString), new Func(r => r.GetBoolean(0)) }; + yield return new object[] { SqlDbType.SmallDateTime, DateTime.Now, new Func(r => r.GetDateTime(0)) }; + yield return new object[] { SqlDbType.TinyInt, (byte)1, new Func(r => r.GetByte(0)) }; } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 3b71cbf851..378a32c07c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -9,6 +9,9 @@ using System.Data.SqlTypes; using System.Threading; using Xunit; +#if NET6_0_OR_GREATER +using Microsoft.Data.SqlClient.Server; +#endif namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -306,6 +309,56 @@ public static void TestParametersWithDatatablesTVPInsert() } } +#if NET6_0_OR_GREATER + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void TestDateOnlyTVPDataTable_CommandSP() + { + string tableTypeName = "[dbo]." + DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDateOnlyTVP"); + string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDateOnlyTVP"); + SqlConnection connection = new(s_connString); + try + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; + cmd.ExecuteNonQuery(); + } + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = spName; + cmd.CommandType = CommandType.StoredProcedure; + + DataTable dtTest = new(); + dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); + dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); + var dataRow = dtTest.NewRow(); + dataRow["DateColumn"] = new DateOnly(2023, 11, 15); + dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); + dtTest.Rows.Add(dataRow); + + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = tableTypeName, + Value = dtTest, + }); + + cmd.ExecuteNonQuery(); + } + } + finally + { + DataTestUtility.DropStoredProcedure(connection, spName); + DataTestUtility.DropUserDefinedType(connection, tableTypeName); + } + } +#endif + #region Scaled Decimal Parameter & TVP Test [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("CAST(1.0 as decimal(38, 37))", "1.0000000000000000000000000000")] diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 9c58ec3889..222ad1c1e6 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -29,8 +29,8 @@ 1.10.3 4.56.0 - 6.35.0 - 6.35.0 + 7.5.0 + 7.5.0 4.5.1 6.0.0 8.0.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index f44a2a462d..c2c4353999 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -31,8 +31,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -42,8 +42,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -52,8 +52,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -62,8 +62,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + @@ -80,8 +80,8 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + 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 6c2ea69cbf..b651ad91b8 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -58,42 +58,42 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti - - - - - + + + + + - - - + + + - - - + + + - + - - - + + + - - - + + + - + - - - + + + - - - + + + - +