diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 8bcde2552d0c..29e9664469c2 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -431,6 +431,10 @@ jobs: - bash: "./eng/scripts/install-nginx-mac.sh" displayName: Installing Nginx afterBuild: + - bash: ./build.sh --ci --pack --no-build --no-restore --no-build-deps "/bl:artifacts/log/packages.pack.binlog" + displayName: Pack Packages (for Template tests) + - bash: ./src/ProjectTemplates/build.sh --ci --pack --no-restore --no-build-deps "/bl:artifacts/log/template.pack.binlog" + displayName: Pack Templates (for Template tests) - bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true displayName: Run Flaky Tests continueOnError: true @@ -456,6 +460,10 @@ jobs: - bash: "echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p" displayName: Increase inotify limit afterBuild: + - bash: ./build.sh --ci --pack --no-build --no-restore --no-build-deps "/bl:artifacts/log/packages.pack.binlog" + displayName: Pack Packages (for Template tests) + - bash: ./src/ProjectTemplates/build.sh --ci --pack --no-restore --no-build-deps "/bl:artifacts/log/template.pack.binlog" + displayName: Pack Templates (for Template tests) - bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true displayName: Run Flaky Tests continueOnError: true diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml index cee1da9be24c..26c116582f26 100644 --- a/.azure/pipelines/jobs/default-build.yml +++ b/.azure/pipelines/jobs/default-build.yml @@ -104,12 +104,8 @@ jobs: queue: BuildPool.Windows.10.Amd64.VS2019.Open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: NetCoreInternal-Pool - ${{ if ne(parameters.isTestingJob, true) }}: - # Visual Studio Build Tools - queue: BuildPool.Windows.10.Amd64.VS2019.BT - ${{ if eq(parameters.isTestingJob, true) }}: - # Visual Studio Enterprise - contains some stuff, like SQL Server and IIS Express, that we use for testing - queue: BuildPool.Windows.10.Amd64.VS2019 + # Visual Studio Enterprise - contains some stuff, like SQL Server and IIS Express, that we use for testing + queue: BuildPool.Windows.10.Amd64.VS2019 variables: AgentOsName: ${{ parameters.agentOs }} ASPNETCORE_TEST_LOG_MAXPATH: "200" # Keep test log file name length low enough for artifact zipping diff --git a/Directory.Build.props b/Directory.Build.props index 35aaa6a2aeb3..d7c5acd6b090 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -70,6 +70,10 @@ Microsoft.AspNetCore.App Shared Framework for hosting of Microsoft ASP.NET Core applications. It is open source, cross-platform and is supported by Microsoft. We hope you enjoy using it! If you do, please consider joining the active community of developers that are contributing to the project on GitHub ($(RepositoryUrl)). We happily accept issues and PRs. + .NETCoreApp + netcoreapp$(AspNetCoreMajorMinorVersion) + ASP.NET Core $(AspNetCoreMajorMinorVersion) + Microsoft.AspNetCore.App.Ref aspnetcore-runtime aspnetcore-targeting-pack diff --git a/build.ps1 b/build.ps1 index daa376269f82..018c3970b759 100644 --- a/build.ps1 +++ b/build.ps1 @@ -324,6 +324,7 @@ if ($tmpBinaryLog) { # Capture MSBuild crash logs $env:MSBUILDDEBUGPATH = $LogDir +$local:exit_code = $null try { # Import custom tools configuration, if present in the repo. # Note: Import in global scope so that the script set top-level variables without qualification. @@ -342,6 +343,10 @@ try { $restore = $tmpRestore + if ($ci) { + $global:VerbosePreference = 'Continue' + } + if (-not $NoBuildRepoTasks) { MSBuild $toolsetBuildProj ` /p:RepoRoot=$RepoRoot ` @@ -359,9 +364,13 @@ try { catch { Write-Host $_.ScriptStackTrace Write-PipelineTaskError -Message $_ - ExitWithExitCode 1 + $exit_code = 1 } finally { + if (! $exit_code) { + $exit_code = $LASTEXITCODE + } + # tools.ps1 corrupts global state, so reset these values so they don't carry between invocations of build.ps1 rm variable:global:_BuildTool -ea Ignore rm variable:global:_DotNetInstallDir -ea Ignore @@ -378,4 +387,4 @@ finally { } } -ExitWithExitCode 0 +ExitWithExitCode $exit_code diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props index afd88d32a3c7..a6390c8a4d4d 100644 --- a/eng/SharedFramework.External.props +++ b/eng/SharedFramework.External.props @@ -91,6 +91,9 @@ This references are part of Microsoft.NETCore.App, so are listed here as references to be used during compilation only. --> + <_CompilationOnlyReference Include="Microsoft.Win32.Registry" /> + <_CompilationOnlyReference Include="System.Security.Cryptography.Cng" /> + <_CompilationOnlyReference Include="System.Security.Principal.Windows" /> <_CompilationOnlyReference Include="System.Buffers" /> <_CompilationOnlyReference Include="System.ComponentModel.Annotations" /> <_CompilationOnlyReference Include="System.Runtime.CompilerServices.Unsafe" /> diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props index 065172aad2e5..798e1469f270 100644 --- a/eng/SharedFramework.Local.props +++ b/eng/SharedFramework.Local.props @@ -8,8 +8,15 @@ + + + + + + + @@ -20,11 +27,6 @@ - - - - - @@ -40,8 +42,6 @@ - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8ab0f8698365..b5c55fc5d359 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,25 +9,25 @@ --> - + https://github.com/aspnet/Blazor - fc20643ca2493c56180e4f5a00946116a3b6afdc + 03f3e4a1a88037d9bbdd21334706ff147d616148 - + https://github.com/aspnet/AspNetCore-Tooling - 271045bfd76b339ab1c8b2555507ad727b068aa3 + a399bd072e04f394de843ac100bd16fdd926a993 - + https://github.com/aspnet/AspNetCore-Tooling - 271045bfd76b339ab1c8b2555507ad727b068aa3 + a399bd072e04f394de843ac100bd16fdd926a993 - + https://github.com/aspnet/AspNetCore-Tooling - 271045bfd76b339ab1c8b2555507ad727b068aa3 + a399bd072e04f394de843ac100bd16fdd926a993 - + https://github.com/aspnet/AspNetCore-Tooling - 271045bfd76b339ab1c8b2555507ad727b068aa3 + a399bd072e04f394de843ac100bd16fdd926a993 https://github.com/aspnet/EntityFrameworkCore diff --git a/eng/Versions.props b/eng/Versions.props index 3db6ef368f4c..e3fa0ec0f15a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -82,7 +82,7 @@ 3.0.0-preview7.19312.3 - 0.10.0-preview7.19310.1 + 0.10.0-preview7.19317.1 3.0.0-preview7.19312.4 3.0.0-preview7.19312.4 @@ -154,10 +154,10 @@ 3.0.0-preview7.19313.2 3.0.0-preview7.19313.2 - 3.0.0-preview7.19316.1 - 3.0.0-preview7.19316.1 - 3.0.0-preview7.19316.1 - 3.0.0-preview7.19316.1 + 3.0.0-preview7.19320.3 + 3.0.0-preview7.19320.3 + 3.0.0-preview7.19320.3 + 3.0.0-preview7.19320.3 - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/Abstractions/ref/Microsoft.AspNetCore.DataProtection.Abstractions.netcoreapp3.0.cs b/src/DataProtection/Abstractions/ref/Microsoft.AspNetCore.DataProtection.Abstractions.netstandard2.0.cs similarity index 100% rename from src/DataProtection/Abstractions/ref/Microsoft.AspNetCore.DataProtection.Abstractions.netcoreapp3.0.cs rename to src/DataProtection/Abstractions/ref/Microsoft.AspNetCore.DataProtection.Abstractions.netstandard2.0.cs diff --git a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj index 14606b7db75a..8b7fb04cd759 100644 --- a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj +++ b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj @@ -5,8 +5,9 @@ Commonly used types: Microsoft.AspNetCore.DataProtection.IDataProtectionProvider Microsoft.AspNetCore.DataProtection.IDataProtector - netcoreapp3.0 + netstandard2.0 true + true true aspnetcore;dataprotection diff --git a/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj index a13e3ec8cd13..8ed2e9d229e7 100644 --- a/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj +++ b/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.netcoreapp3.0.cs b/src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.netstandard2.0.cs similarity index 100% rename from src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.netcoreapp3.0.cs rename to src/DataProtection/AzureKeyVault/ref/Microsoft.AspNetCore.DataProtection.AzureKeyVault.netstandard2.0.cs diff --git a/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj index 43dd5ee5840f..2f1aa7c4c0e4 100644 --- a/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj +++ b/src/DataProtection/AzureKeyVault/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj @@ -2,7 +2,7 @@ Microsoft Azure KeyVault key encryption support. - netcoreapp3.0 + netstandard2.0 true aspnetcore;dataprotection;azure;keyvault true diff --git a/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj index dc9fa4eb0910..07cb2861f0f3 100644 --- a/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj +++ b/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.netcoreapp3.0.cs b/src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.netstandard2.0.cs similarity index 100% rename from src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.netcoreapp3.0.cs rename to src/DataProtection/AzureStorage/ref/Microsoft.AspNetCore.DataProtection.AzureStorage.netstandard2.0.cs diff --git a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj index ea12eab5e9b7..8272cd539b5e 100644 --- a/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj +++ b/src/DataProtection/AzureStorage/src/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj @@ -2,7 +2,7 @@ Microsoft Azure Blob storage support as key store. - netcoreapp3.0 + netstandard2.0 true true aspnetcore;dataprotection;azure;blob diff --git a/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.csproj b/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.csproj index 8718765fd74a..ec4ae22e0e8f 100644 --- a/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.csproj +++ b/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.netcoreapp3.0.cs b/src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.netstandard2.0.cs similarity index 100% rename from src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.netcoreapp3.0.cs rename to src/DataProtection/Cryptography.Internal/ref/Microsoft.AspNetCore.Cryptography.Internal.netstandard2.0.cs diff --git a/src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj b/src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj index 1ee230c0e117..9fbec5f398e2 100644 --- a/src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj +++ b/src/DataProtection/Cryptography.Internal/src/Microsoft.AspNetCore.Cryptography.Internal.csproj @@ -2,8 +2,9 @@ Infrastructure for ASP.NET Core cryptographic packages. Applications and libraries should not reference this package directly. - netcoreapp3.0 + netstandard2.0 true + true $(NoWarn);CS1591 true true diff --git a/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj b/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj index 5f59ea823da8..0dd3dbf34c1c 100644 --- a/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj +++ b/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj @@ -1,10 +1,14 @@ - netcoreapp3.0 + netstandard2.0;netcoreapp2.0 - - + + + + + + diff --git a/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp3.0.cs b/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp2.0.cs similarity index 100% rename from src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp3.0.cs rename to src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp2.0.cs diff --git a/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netstandard2.0.cs b/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netstandard2.0.cs new file mode 100644 index 000000000000..4ca6f16d11d6 --- /dev/null +++ b/src/DataProtection/Cryptography.KeyDerivation/ref/Microsoft.AspNetCore.Cryptography.KeyDerivation.netstandard2.0.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Cryptography.KeyDerivation +{ + public static partial class KeyDerivation + { + public static byte[] Pbkdf2(string password, byte[] salt, Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf prf, int iterationCount, int numBytesRequested) { throw null; } + } + public enum KeyDerivationPrf + { + HMACSHA1 = 0, + HMACSHA256 = 1, + HMACSHA512 = 2, + } +} diff --git a/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj b/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj index 016ac5a57a7d..dea0ee73e28f 100644 --- a/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj +++ b/src/DataProtection/Cryptography.KeyDerivation/src/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj @@ -2,8 +2,9 @@ ASP.NET Core utilities for key derivation. - netcoreapp3.0 + netstandard2.0;netcoreapp2.0 true + true true true aspnetcore;dataprotection diff --git a/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/NetCorePbkdf2Provider.cs b/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/NetCorePbkdf2Provider.cs index ace4fd2be1d0..11b8f481bb82 100644 --- a/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/NetCorePbkdf2Provider.cs +++ b/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/NetCorePbkdf2Provider.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#if NETCOREAPP2_0 using System; using System.Diagnostics; using System.Security.Cryptography; @@ -61,3 +62,4 @@ private static byte[] DeriveKeyImpl(string password, byte[] salt, KeyDerivationP } } } +#endif diff --git a/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/Pbkdf2Util.cs b/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/Pbkdf2Util.cs index 7cbda18bdd53..9b2ecf10089a 100644 --- a/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/Pbkdf2Util.cs +++ b/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/Pbkdf2Util.cs @@ -27,9 +27,15 @@ private static IPbkdf2Provider GetPbkdf2Provider() } else { +#if NETSTANDARD2_0 + return new ManagedPbkdf2Provider(); +#elif NETCOREAPP2_0 // fastest implementation on .NET Core for Linux/macOS. // Not supported on .NET Framework return new NetCorePbkdf2Provider(); +#else +#error Update target frameworks +#endif } } } diff --git a/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.csproj b/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.csproj index adcfda7a042c..f9c576c50102 100644 --- a/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.csproj +++ b/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.csproj @@ -1,14 +1,14 @@ - netcoreapp3.0 + netstandard2.0 - - + + - + diff --git a/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.netcoreapp3.0.cs b/src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.netstandard2.0.cs similarity index 100% rename from src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.netcoreapp3.0.cs rename to src/DataProtection/DataProtection/ref/Microsoft.AspNetCore.DataProtection.netstandard2.0.cs diff --git a/src/DataProtection/DataProtection/src/DataProtectionServiceCollectionExtensions.cs b/src/DataProtection/DataProtection/src/DataProtectionServiceCollectionExtensions.cs index b112e9ac68ef..e6a3d2ceee90 100644 --- a/src/DataProtection/DataProtection/src/DataProtectionServiceCollectionExtensions.cs +++ b/src/DataProtection/DataProtection/src/DataProtectionServiceCollectionExtensions.cs @@ -9,8 +9,8 @@ using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.DataProtection.XmlEncryption; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -77,7 +77,7 @@ private static void AddDataProtectionServices(IServiceCollection services) services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); // Internal services services.TryAddSingleton(); diff --git a/src/DataProtection/DataProtection/src/Internal/DataProtectionStartupFilter.cs b/src/DataProtection/DataProtection/src/Internal/DataProtectionHostedService.cs similarity index 66% rename from src/DataProtection/DataProtection/src/Internal/DataProtectionStartupFilter.cs rename to src/DataProtection/DataProtection/src/Internal/DataProtectionHostedService.cs index d9faa5b0f8eb..c31f56e05903 100644 --- a/src/DataProtection/DataProtection/src/Internal/DataProtectionStartupFilter.cs +++ b/src/DataProtection/DataProtection/src/Internal/DataProtectionHostedService.cs @@ -1,31 +1,32 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Builder; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; -using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.DataProtection.Internal { - internal class DataProtectionStartupFilter : IStartupFilter + internal class DataProtectionHostedService : IHostedService { private readonly IKeyRingProvider _keyRingProvider; - private readonly ILogger _logger; + private readonly ILogger _logger; - public DataProtectionStartupFilter(IKeyRingProvider keyRingProvider) + public DataProtectionHostedService(IKeyRingProvider keyRingProvider) : this(keyRingProvider, NullLoggerFactory.Instance) { } - public DataProtectionStartupFilter(IKeyRingProvider keyRingProvider, ILoggerFactory loggerFactory) + public DataProtectionHostedService(IKeyRingProvider keyRingProvider, ILoggerFactory loggerFactory) { _keyRingProvider = keyRingProvider; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); } - public Action Configure(Action next) + public Task StartAsync(CancellationToken token) { try { @@ -42,7 +43,9 @@ public Action Configure(Action next) _logger.KeyRingFailedToLoadOnStartup(ex); } - return next; + return Task.CompletedTask; } + + public Task StopAsync(CancellationToken token) => Task.CompletedTask; } } diff --git a/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj b/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj index 067f987981df..1c5f507cc273 100644 --- a/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj +++ b/src/DataProtection/DataProtection/src/Microsoft.AspNetCore.DataProtection.csproj @@ -2,8 +2,9 @@ ASP.NET Core logic to protect and unprotect data, similar to DPAPI. - netcoreapp3.0 + netstandard2.0 true + true $(NoWarn);CS1591 true true @@ -17,13 +18,14 @@ - + + diff --git a/src/DataProtection/DataProtection/test/HostingTests.cs b/src/DataProtection/DataProtection/test/HostingTests.cs index cd43effe3768..c687715a4c33 100644 --- a/src/DataProtection/DataProtection/test/HostingTests.cs +++ b/src/DataProtection/DataProtection/test/HostingTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Testing; @@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.DataProtection.Test public class HostingTests { [Fact] - public async Task LoadsKeyRingBeforeServerStarts() + public async Task WebhostLoadsKeyRingBeforeServerStarts() { var tcs = new TaskCompletionSource(); var mockKeyRing = new Mock(); @@ -46,6 +47,33 @@ public async Task LoadsKeyRingBeforeServerStarts() mockKeyRing.VerifyAll(); } + [Fact] + public async Task GenericHostLoadsKeyRingBeforeServerStarts() + { + var tcs = new TaskCompletionSource(); + var mockKeyRing = new Mock(); + mockKeyRing.Setup(m => m.GetCurrentKeyRing()) + .Returns(Mock.Of()) + .Callback(() => tcs.TrySetResult(null)); + + var builder = new HostBuilder() + .ConfigureServices(s => + s.AddDataProtection() + .Services + .Replace(ServiceDescriptor.Singleton(mockKeyRing.Object)) + .AddSingleton( + new FakeServer(onStart: () => tcs.TrySetException(new InvalidOperationException("Server was started before key ring was initialized"))))) + .ConfigureWebHost(b => b.UseStartup()); + + using (var host = builder.Build()) + { + await host.StartAsync(); + } + + await tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); + mockKeyRing.VerifyAll(); + } + [Fact] public async Task StartupContinuesOnFailureToLoadKey() { diff --git a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj index fc84cc88a29f..2e4c664234a0 100644 --- a/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj +++ b/src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -15,6 +15,7 @@ + diff --git a/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj index dbc6e58ab8c1..dd34dcfdfd50 100644 --- a/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj +++ b/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.1 - - + + diff --git a/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.netcoreapp3.0.cs b/src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.netstandard2.1.cs similarity index 100% rename from src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.netcoreapp3.0.cs rename to src/DataProtection/EntityFrameworkCore/ref/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.netstandard2.1.cs diff --git a/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj index 8493504ffb09..58efeab48067 100644 --- a/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj +++ b/src/DataProtection/EntityFrameworkCore/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj @@ -2,7 +2,7 @@ Support for storing keys using Entity Framework Core. - netcoreapp3.0 + netstandard2.1 true true aspnetcore;dataprotection;entityframeworkcore diff --git a/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.csproj b/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.csproj index 01a99671f067..82cadaff7a49 100644 --- a/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.csproj +++ b/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.netcoreapp3.0.cs b/src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.netstandard2.0.cs similarity index 100% rename from src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.netcoreapp3.0.cs rename to src/DataProtection/Extensions/ref/Microsoft.AspNetCore.DataProtection.Extensions.netstandard2.0.cs diff --git a/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj b/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj index fff474980f96..1a0a65d3fa28 100644 --- a/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj +++ b/src/DataProtection/Extensions/src/Microsoft.AspNetCore.DataProtection.Extensions.csproj @@ -2,8 +2,9 @@ Additional APIs for ASP.NET Core data protection. - netcoreapp3.0 + netstandard2.0 true + true true aspnetcore;dataprotection diff --git a/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj b/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj index 927f13c24d25..013fffddf8c7 100644 --- a/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj +++ b/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.netcoreapp3.0.cs b/src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.netstandard2.0.cs similarity index 100% rename from src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.netcoreapp3.0.cs rename to src/DataProtection/StackExchangeRedis/ref/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.netstandard2.0.cs diff --git a/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj b/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj index af2d8cbe7e9f..ed592f5831d2 100644 --- a/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj +++ b/src/DataProtection/StackExchangeRedis/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj @@ -2,7 +2,7 @@ Support for storing data protection keys in Redis. - netcoreapp3.0 + netstandard2.0 true true aspnetcore;dataprotection;redis diff --git a/src/Framework/Directory.Build.props b/src/Framework/Directory.Build.props index 8cf57408e381..2a8b3bf3c031 100644 --- a/src/Framework/Directory.Build.props +++ b/src/Framework/Directory.Build.props @@ -8,4 +8,11 @@ $(ArtifactsObjDir)$(PlatformManifestFileName) + + + + + + + diff --git a/src/Framework/build.cmd b/src/Framework/build.cmd new file mode 100644 index 000000000000..2406296662e9 --- /dev/null +++ b/src/Framework/build.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SET RepoRoot=%~dp0..\.. +%RepoRoot%\build.cmd -projects %~dp0**\*.*proj %* diff --git a/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj b/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj index 6e2ca3f92475..2a73d0fb44fa 100644 --- a/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj +++ b/src/Framework/ref/Microsoft.AspNetCore.App.Ref.csproj @@ -36,6 +36,10 @@ This package is an internal implementation of the .NET Core SDK and is not meant true + + + FrameworkList.xml + $(ArtifactsObjDir)$(FrameworkListFileName) @@ -67,6 +71,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant $(BuildDependsOn); GeneratePackageConflictManifest; _ResolveTargetingPackContent; + IncludeFrameworkListFile; _BatchCopyToLayoutTargetDir; _InstallTargetingPackIntoLocalDotNet; _CreateTargetingPackArchive; @@ -113,8 +118,6 @@ This package is an internal implementation of the .NET Core SDK and is not meant - - <_PackageFiles Include="@(RefPackContent)" /> @@ -174,4 +177,19 @@ This package is an internal implementation of the .NET Core SDK and is not meant + + + + + + <_PackageFiles Include="@(RefPackContent)" /> + + + diff --git a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj index c8a6be74e951..034b03739394 100644 --- a/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj +++ b/src/Framework/src/Microsoft.AspNetCore.App.Runtime.csproj @@ -100,6 +100,9 @@ This package is an internal implementation of the .NET Core SDK and is not meant $([MSBuild]::EnsureTrailingSlash('$(RuntimePackageRoot)')) $([System.IO.Path]::Combine('$(RuntimePackageRoot)', 'tools', '$(CrossgenToolPackagePath)')) $(AssetTargetFallback);native,Version=0.0 + + RuntimeList.xml + $(ArtifactsObjDir)$(FrameworkListFileName) @@ -154,6 +157,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant GenerateSharedFxVersionsFiles; Crossgen; _ResolveSharedFrameworkContent; + IncludeFrameworkListFile; _DownloadAndExtractDotNetRuntime; _BatchCopyToSharedFrameworkLayout; _BatchCopyToRedistLayout; @@ -275,8 +279,6 @@ This package is an internal implementation of the .NET Core SDK and is not meant DependsOnTargets="GenerateSharedFxDepsFile"> - - <_PackageFiles Include="@(RuntimePackContent)" /> @@ -448,4 +450,19 @@ This package is an internal implementation of the .NET Core SDK and is not meant Condition="'$(ArchiveExtension)' == '.tar.gz'" /> + + + + + + <_PackageFiles Include="@(RuntimePackContent)" /> + + + diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index ec6d3d84a09d..57b974a80002 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -149,6 +149,10 @@ public virtual async Task StartAsync(CancellationToken cancellationToken = defau _applicationLifetime = _applicationServices.GetRequiredService(); _hostedServiceExecutor = _applicationServices.GetRequiredService(); + + // Fire IHostedService.Start + await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); + var diagnosticSource = _applicationServices.GetRequiredService(); var httpContextFactory = _applicationServices.GetRequiredService(); var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory); @@ -158,8 +162,6 @@ public virtual async Task StartAsync(CancellationToken cancellationToken = defau // Fire IApplicationLifetime.Started _applicationLifetime?.NotifyStarted(); - // Fire IHostedService.Start - await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); _logger.Started(); @@ -339,7 +341,7 @@ public async Task StopAsync(CancellationToken cancellationToken = default) } // Fire the IHostedService.Stop - if (_hostedServiceExecutor != null && _startedServer) + if (_hostedServiceExecutor != null) { await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false); } diff --git a/src/Hosting/Hosting/test/WebHostTests.cs b/src/Hosting/Hosting/test/WebHostTests.cs index 3d4a67b0769e..cdc53386daa3 100644 --- a/src/Hosting/Hosting/test/WebHostTests.cs +++ b/src/Hosting/Hosting/test/WebHostTests.cs @@ -702,8 +702,8 @@ public async Task WebHostDoesNotNotifyAllIHostedServicesAndIApplicationLifetimeC await Assert.ThrowsAsync(() => host.StartAsync()); Assert.True(hostedServiceCalls1[0]); Assert.False(hostedServiceCalls2[0]); - Assert.True(started.All(s => s)); - Assert.True(started2.All(s => s)); + Assert.False(started.All(s => s)); // Server doesn't start if hosted services throw + Assert.False(started2.All(s => s)); host.Dispose(); Assert.True(hostedServiceCalls1[1]); Assert.True(hostedServiceCalls2[1]); diff --git a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.csproj b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.csproj index 55b8c20bcca9..29f919fbe6ba 100644 --- a/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.csproj +++ b/src/Identity/Core/ref/Microsoft.AspNetCore.Identity.csproj @@ -6,8 +6,6 @@ - - diff --git a/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj b/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj index 6006e4fa1ed0..1804604b76be 100644 --- a/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj +++ b/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj @@ -10,8 +10,6 @@ - - diff --git a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj index 1fd47b56f5eb..50615ee4e140 100644 --- a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj +++ b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.csproj @@ -1,9 +1,15 @@ - netcoreapp3.0 + netstandard2.0;netcoreapp3.0 - + + + + + + + diff --git a/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netstandard2.0.cs b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netstandard2.0.cs new file mode 100644 index 000000000000..f934eb8db2b7 --- /dev/null +++ b/src/Identity/Extensions.Core/ref/Microsoft.Extensions.Identity.Core.netstandard2.0.cs @@ -0,0 +1,707 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Identity +{ + public partial class AuthenticatorTokenProvider : Microsoft.AspNetCore.Identity.IUserTwoFactorTokenProvider where TUser : class + { + public AuthenticatorTokenProvider() { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CanGenerateTwoFactorTokenAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + public virtual System.Threading.Tasks.Task GenerateAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ValidateAsync(string purpose, string token, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } + public partial class ClaimsIdentityOptions + { + public ClaimsIdentityOptions() { } + public string RoleClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string SecurityStampClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string UserIdClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string UserNameClaimType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class DefaultPersonalDataProtector : Microsoft.AspNetCore.Identity.IPersonalDataProtector + { + public DefaultPersonalDataProtector(Microsoft.AspNetCore.Identity.ILookupProtectorKeyRing keyRing, Microsoft.AspNetCore.Identity.ILookupProtector protector) { } + public virtual string Protect(string data) { throw null; } + public virtual string Unprotect(string data) { throw null; } + } + public partial class DefaultUserConfirmation : Microsoft.AspNetCore.Identity.IUserConfirmation where TUser : class + { + public DefaultUserConfirmation() { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } + public partial class EmailTokenProvider : Microsoft.AspNetCore.Identity.TotpSecurityStampBasedTokenProvider where TUser : class + { + public EmailTokenProvider() { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.Task CanGenerateTwoFactorTokenAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.Task GetUserModifierAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } + public partial class IdentityBuilder + { + public IdentityBuilder(System.Type user, Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + public IdentityBuilder(System.Type user, System.Type role, Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + public System.Type RoleType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Type UserType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddClaimsPrincipalFactory() where TFactory : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddErrorDescriber() where TDescriber : Microsoft.AspNetCore.Identity.IdentityErrorDescriber { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddPasswordValidator() where TValidator : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddPersonalDataProtection() where TProtector : class, Microsoft.AspNetCore.Identity.ILookupProtector where TKeyRing : class, Microsoft.AspNetCore.Identity.ILookupProtectorKeyRing { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddRoleManager() where TRoleManager : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddRoleStore() where TStore : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddRoles() where TRole : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddRoleValidator() where TRole : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddTokenProvider(string providerName, System.Type provider) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddTokenProvider(string providerName) where TProvider : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddUserManager() where TUserManager : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddUserStore() where TStore : class { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityBuilder AddUserValidator() where TValidator : class { throw null; } + } + public partial class IdentityError + { + public IdentityError() { } + public string Code { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class IdentityErrorDescriber + { + public IdentityErrorDescriber() { } + public virtual Microsoft.AspNetCore.Identity.IdentityError ConcurrencyFailure() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError DefaultError() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError DuplicateEmail(string email) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError DuplicateRoleName(string role) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError DuplicateUserName(string userName) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError InvalidEmail(string email) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError InvalidRoleName(string role) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError InvalidToken() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError InvalidUserName(string userName) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError LoginAlreadyAssociated() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordMismatch() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordRequiresDigit() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordRequiresLower() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordRequiresNonAlphanumeric() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordRequiresUniqueChars(int uniqueChars) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordRequiresUpper() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError PasswordTooShort(int length) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError RecoveryCodeRedemptionFailed() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError UserAlreadyHasPassword() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError UserAlreadyInRole(string role) { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError UserLockoutNotEnabled() { throw null; } + public virtual Microsoft.AspNetCore.Identity.IdentityError UserNotInRole(string role) { throw null; } + } + public partial class IdentityOptions + { + public IdentityOptions() { } + public Microsoft.AspNetCore.Identity.ClaimsIdentityOptions ClaimsIdentity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.LockoutOptions Lockout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.PasswordOptions Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.SignInOptions SignIn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.StoreOptions Stores { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.TokenOptions Tokens { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.UserOptions User { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class IdentityResult + { + public IdentityResult() { } + public System.Collections.Generic.IEnumerable Errors { get { throw null; } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public static Microsoft.AspNetCore.Identity.IdentityResult Success { get { throw null; } } + public static Microsoft.AspNetCore.Identity.IdentityResult Failed(params Microsoft.AspNetCore.Identity.IdentityError[] errors) { throw null; } + public override string ToString() { throw null; } + } + public partial interface ILookupNormalizer + { + string NormalizeEmail(string email); + string NormalizeName(string name); + } + public partial interface ILookupProtector + { + string Protect(string keyId, string data); + string Unprotect(string keyId, string data); + } + public partial interface ILookupProtectorKeyRing + { + string CurrentKeyId { get; } + string this[string keyId] { get; } + System.Collections.Generic.IEnumerable GetAllKeyIds(); + } + public partial interface IPasswordHasher where TUser : class + { + string HashPassword(TUser user, string password); + Microsoft.AspNetCore.Identity.PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword); + } + public partial interface IPasswordValidator where TUser : class + { + System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user, string password); + } + public partial interface IPersonalDataProtector + { + string Protect(string data); + string Unprotect(string data); + } + public partial interface IProtectedUserStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + } + public partial interface IQueryableRoleStore : Microsoft.AspNetCore.Identity.IRoleStore, System.IDisposable where TRole : class + { + System.Linq.IQueryable Roles { get; } + } + public partial interface IQueryableUserStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Linq.IQueryable Users { get; } + } + public partial interface IRoleClaimStore : Microsoft.AspNetCore.Identity.IRoleStore, System.IDisposable where TRole : class + { + System.Threading.Tasks.Task AddClaimAsync(TRole role, System.Security.Claims.Claim claim, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> GetClaimsAsync(TRole role, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task RemoveClaimAsync(TRole role, System.Security.Claims.Claim claim, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IRoleStore : System.IDisposable where TRole : class + { + System.Threading.Tasks.Task CreateAsync(TRole role, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task DeleteAsync(TRole role, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task FindByIdAsync(string roleId, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task FindByNameAsync(string normalizedRoleName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetNormalizedRoleNameAsync(TRole role, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetRoleIdAsync(TRole role, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetRoleNameAsync(TRole role, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetRoleNameAsync(TRole role, string roleName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task UpdateAsync(TRole role, System.Threading.CancellationToken cancellationToken); + } + public partial interface IRoleValidator where TRole : class + { + System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.RoleManager manager, TRole role); + } + public partial interface IUserAuthenticationTokenStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetTokenAsync(TUser user, string loginProvider, string name, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task RemoveTokenAsync(TUser user, string loginProvider, string name, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetTokenAsync(TUser user, string loginProvider, string name, string value, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserAuthenticatorKeyStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetAuthenticatorKeyAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetAuthenticatorKeyAsync(TUser user, string key, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserClaimsPrincipalFactory where TUser : class + { + System.Threading.Tasks.Task CreateAsync(TUser user); + } + public partial interface IUserClaimStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task AddClaimsAsync(TUser user, System.Collections.Generic.IEnumerable claims, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task> GetClaimsAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task> GetUsersForClaimAsync(System.Security.Claims.Claim claim, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task RemoveClaimsAsync(TUser user, System.Collections.Generic.IEnumerable claims, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task ReplaceClaimAsync(TUser user, System.Security.Claims.Claim claim, System.Security.Claims.Claim newClaim, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserConfirmation where TUser : class + { + System.Threading.Tasks.Task IsConfirmedAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + } + public partial interface IUserEmailStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task FindByEmailAsync(string normalizedEmail, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetEmailAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetEmailConfirmedAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetNormalizedEmailAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetEmailAsync(TUser user, string email, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetEmailConfirmedAsync(TUser user, bool confirmed, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserLockoutStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetAccessFailedCountAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetLockoutEnabledAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetLockoutEndDateAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task IncrementAccessFailedCountAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task ResetAccessFailedCountAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetLockoutEnabledAsync(TUser user, bool enabled, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetLockoutEndDateAsync(TUser user, System.DateTimeOffset? lockoutEnd, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserLoginStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task AddLoginAsync(TUser user, Microsoft.AspNetCore.Identity.UserLoginInfo login, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task FindByLoginAsync(string loginProvider, string providerKey, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task> GetLoginsAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserPasswordStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetPasswordHashAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task HasPasswordAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetPasswordHashAsync(TUser user, string passwordHash, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserPhoneNumberStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetPhoneNumberAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetPhoneNumberConfirmedAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetPhoneNumberAsync(TUser user, string phoneNumber, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserRoleStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task AddToRoleAsync(TUser user, string roleName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task> GetRolesAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task> GetUsersInRoleAsync(string roleName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task IsInRoleAsync(TUser user, string roleName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task RemoveFromRoleAsync(TUser user, string roleName, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserSecurityStampStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetSecurityStampAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetSecurityStampAsync(TUser user, string stamp, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserStore : System.IDisposable where TUser : class + { + System.Threading.Tasks.Task CreateAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task DeleteAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task FindByIdAsync(string userId, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task FindByNameAsync(string normalizedUserName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetNormalizedUserNameAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetUserIdAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task GetUserNameAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetNormalizedUserNameAsync(TUser user, string normalizedName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetUserNameAsync(TUser user, string userName, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task UpdateAsync(TUser user, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserTwoFactorRecoveryCodeStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task CountCodesAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task RedeemCodeAsync(TUser user, string code, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task ReplaceCodesAsync(TUser user, System.Collections.Generic.IEnumerable recoveryCodes, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserTwoFactorStore : Microsoft.AspNetCore.Identity.IUserStore, System.IDisposable where TUser : class + { + System.Threading.Tasks.Task GetTwoFactorEnabledAsync(TUser user, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task SetTwoFactorEnabledAsync(TUser user, bool enabled, System.Threading.CancellationToken cancellationToken); + } + public partial interface IUserTwoFactorTokenProvider where TUser : class + { + System.Threading.Tasks.Task CanGenerateTwoFactorTokenAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + System.Threading.Tasks.Task GenerateAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + System.Threading.Tasks.Task ValidateAsync(string purpose, string token, Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + } + public partial interface IUserValidator where TUser : class + { + System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + } + public partial class LockoutOptions + { + public LockoutOptions() { } + public bool AllowedForNewUsers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan DefaultLockoutTimeSpan { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int MaxFailedAccessAttempts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public enum PasswordHasherCompatibilityMode + { + IdentityV2 = 0, + IdentityV3 = 1, + } + public partial class PasswordHasherOptions + { + public PasswordHasherOptions() { } + public Microsoft.AspNetCore.Identity.PasswordHasherCompatibilityMode CompatibilityMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int IterationCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class PasswordHasher : Microsoft.AspNetCore.Identity.IPasswordHasher where TUser : class + { + public PasswordHasher(Microsoft.Extensions.Options.IOptions optionsAccessor = null) { } + public virtual string HashPassword(TUser user, string password) { throw null; } + public virtual Microsoft.AspNetCore.Identity.PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword) { throw null; } + } + public partial class PasswordOptions + { + public PasswordOptions() { } + public bool RequireDigit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int RequiredLength { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int RequiredUniqueChars { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireLowercase { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireNonAlphanumeric { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireUppercase { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class PasswordValidator : Microsoft.AspNetCore.Identity.IPasswordValidator where TUser : class + { + public PasswordValidator(Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors = null) { } + public Microsoft.AspNetCore.Identity.IdentityErrorDescriber Describer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual bool IsDigit(char c) { throw null; } + public virtual bool IsLetterOrDigit(char c) { throw null; } + public virtual bool IsLower(char c) { throw null; } + public virtual bool IsUpper(char c) { throw null; } + public virtual System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user, string password) { throw null; } + } + public enum PasswordVerificationResult + { + Failed = 0, + Success = 1, + SuccessRehashNeeded = 2, + } + public partial class PersonalDataAttribute : System.Attribute + { + public PersonalDataAttribute() { } + } + public partial class PhoneNumberTokenProvider : Microsoft.AspNetCore.Identity.TotpSecurityStampBasedTokenProvider where TUser : class + { + public PhoneNumberTokenProvider() { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.Task CanGenerateTwoFactorTokenAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.Task GetUserModifierAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } + public partial class ProtectedPersonalDataAttribute : Microsoft.AspNetCore.Identity.PersonalDataAttribute + { + public ProtectedPersonalDataAttribute() { } + } + public partial class RoleManager : System.IDisposable where TRole : class + { + public RoleManager(Microsoft.AspNetCore.Identity.IRoleStore store, System.Collections.Generic.IEnumerable> roleValidators, Microsoft.AspNetCore.Identity.ILookupNormalizer keyNormalizer, Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors, Microsoft.Extensions.Logging.ILogger> logger) { } + protected virtual System.Threading.CancellationToken CancellationToken { get { throw null; } } + public Microsoft.AspNetCore.Identity.IdentityErrorDescriber ErrorDescriber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.ILookupNormalizer KeyNormalizer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Linq.IQueryable Roles { get { throw null; } } + public System.Collections.Generic.IList> RoleValidators { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected Microsoft.AspNetCore.Identity.IRoleStore Store { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual bool SupportsQueryableRoles { get { throw null; } } + public virtual bool SupportsRoleClaims { get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddClaimAsync(TRole role, System.Security.Claims.Claim claim) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CreateAsync(TRole role) { throw null; } + public virtual System.Threading.Tasks.Task DeleteAsync(TRole role) { throw null; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + public virtual System.Threading.Tasks.Task FindByIdAsync(string roleId) { throw null; } + public virtual System.Threading.Tasks.Task FindByNameAsync(string roleName) { throw null; } + public virtual System.Threading.Tasks.Task> GetClaimsAsync(TRole role) { throw null; } + public virtual System.Threading.Tasks.Task GetRoleIdAsync(TRole role) { throw null; } + public virtual System.Threading.Tasks.Task GetRoleNameAsync(TRole role) { throw null; } + public virtual string NormalizeKey(string key) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveClaimAsync(TRole role, System.Security.Claims.Claim claim) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RoleExistsAsync(string roleName) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetRoleNameAsync(TRole role, string name) { throw null; } + protected void ThrowIfDisposed() { } + public virtual System.Threading.Tasks.Task UpdateAsync(TRole role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task UpdateNormalizedRoleNameAsync(TRole role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected virtual System.Threading.Tasks.Task UpdateRoleAsync(TRole role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected virtual System.Threading.Tasks.Task ValidateRoleAsync(TRole role) { throw null; } + } + public partial class RoleValidator : Microsoft.AspNetCore.Identity.IRoleValidator where TRole : class + { + public RoleValidator(Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors = null) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.RoleManager manager, TRole role) { throw null; } + } + public partial class SignInOptions + { + public SignInOptions() { } + public bool RequireConfirmedAccount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireConfirmedEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireConfirmedPhoneNumber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class SignInResult + { + public SignInResult() { } + public static Microsoft.AspNetCore.Identity.SignInResult Failed { get { throw null; } } + public bool IsLockedOut { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public bool IsNotAllowed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public static Microsoft.AspNetCore.Identity.SignInResult LockedOut { get { throw null; } } + public static Microsoft.AspNetCore.Identity.SignInResult NotAllowed { get { throw null; } } + public bool RequiresTwoFactor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]protected set { } } + public static Microsoft.AspNetCore.Identity.SignInResult Success { get { throw null; } } + public static Microsoft.AspNetCore.Identity.SignInResult TwoFactorRequired { get { throw null; } } + public override string ToString() { throw null; } + } + public partial class StoreOptions + { + public StoreOptions() { } + public int MaxLengthForKeys { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool ProtectPersonalData { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class TokenOptions + { + public static readonly string DefaultAuthenticatorProvider; + public static readonly string DefaultEmailProvider; + public static readonly string DefaultPhoneProvider; + public static readonly string DefaultProvider; + public TokenOptions() { } + public string AuthenticatorIssuer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string AuthenticatorTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ChangeEmailTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ChangePhoneNumberTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string EmailConfirmationTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string PasswordResetTokenProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.Dictionary ProviderMap { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class TokenProviderDescriptor + { + public TokenProviderDescriptor(System.Type type) { } + public object ProviderInstance { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Type ProviderType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public abstract partial class TotpSecurityStampBasedTokenProvider : Microsoft.AspNetCore.Identity.IUserTwoFactorTokenProvider where TUser : class + { + protected TotpSecurityStampBasedTokenProvider() { } + public abstract System.Threading.Tasks.Task CanGenerateTwoFactorTokenAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user); + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GenerateAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetUserModifierAsync(string purpose, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ValidateAsync(string purpose, string token, Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } + public sealed partial class UpperInvariantLookupNormalizer : Microsoft.AspNetCore.Identity.ILookupNormalizer + { + public UpperInvariantLookupNormalizer() { } + public string NormalizeEmail(string email) { throw null; } + public string NormalizeName(string name) { throw null; } + } + public partial class UserClaimsPrincipalFactory : Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory where TUser : class + { + public UserClaimsPrincipalFactory(Microsoft.AspNetCore.Identity.UserManager userManager, Microsoft.Extensions.Options.IOptions optionsAccessor) { } + public Microsoft.AspNetCore.Identity.IdentityOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.AspNetCore.Identity.UserManager UserManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CreateAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected virtual System.Threading.Tasks.Task GenerateClaimsAsync(TUser user) { throw null; } + } + public partial class UserClaimsPrincipalFactory : Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory where TUser : class where TRole : class + { + public UserClaimsPrincipalFactory(Microsoft.AspNetCore.Identity.UserManager userManager, Microsoft.AspNetCore.Identity.RoleManager roleManager, Microsoft.Extensions.Options.IOptions options) : base (default(Microsoft.AspNetCore.Identity.UserManager), default(Microsoft.Extensions.Options.IOptions)) { } + public Microsoft.AspNetCore.Identity.RoleManager RoleManager { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected override System.Threading.Tasks.Task GenerateClaimsAsync(TUser user) { throw null; } + } + public partial class UserLoginInfo + { + public UserLoginInfo(string loginProvider, string providerKey, string displayName) { } + public string LoginProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ProviderDisplayName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string ProviderKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class UserManager : System.IDisposable where TUser : class + { + public const string ChangePhoneNumberTokenPurpose = "ChangePhoneNumber"; + public const string ConfirmEmailTokenPurpose = "EmailConfirmation"; + public const string ResetPasswordTokenPurpose = "ResetPassword"; + public UserManager(Microsoft.AspNetCore.Identity.IUserStore store, Microsoft.Extensions.Options.IOptions optionsAccessor, Microsoft.AspNetCore.Identity.IPasswordHasher passwordHasher, System.Collections.Generic.IEnumerable> userValidators, System.Collections.Generic.IEnumerable> passwordValidators, Microsoft.AspNetCore.Identity.ILookupNormalizer keyNormalizer, Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors, System.IServiceProvider services, Microsoft.Extensions.Logging.ILogger> logger) { } + protected virtual System.Threading.CancellationToken CancellationToken { get { throw null; } } + public Microsoft.AspNetCore.Identity.IdentityErrorDescriber ErrorDescriber { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.ILookupNormalizer KeyNormalizer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual Microsoft.Extensions.Logging.ILogger Logger { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.IdentityOptions Options { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.AspNetCore.Identity.IPasswordHasher PasswordHasher { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Collections.Generic.IList> PasswordValidators { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + protected internal Microsoft.AspNetCore.Identity.IUserStore Store { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual bool SupportsQueryableUsers { get { throw null; } } + public virtual bool SupportsUserAuthenticationTokens { get { throw null; } } + public virtual bool SupportsUserAuthenticatorKey { get { throw null; } } + public virtual bool SupportsUserClaim { get { throw null; } } + public virtual bool SupportsUserEmail { get { throw null; } } + public virtual bool SupportsUserLockout { get { throw null; } } + public virtual bool SupportsUserLogin { get { throw null; } } + public virtual bool SupportsUserPassword { get { throw null; } } + public virtual bool SupportsUserPhoneNumber { get { throw null; } } + public virtual bool SupportsUserRole { get { throw null; } } + public virtual bool SupportsUserSecurityStamp { get { throw null; } } + public virtual bool SupportsUserTwoFactor { get { throw null; } } + public virtual bool SupportsUserTwoFactorRecoveryCodes { get { throw null; } } + public virtual System.Linq.IQueryable Users { get { throw null; } } + public System.Collections.Generic.IList> UserValidators { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AccessFailedAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task AddClaimAsync(TUser user, System.Security.Claims.Claim claim) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddClaimsAsync(TUser user, System.Collections.Generic.IEnumerable claims) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddLoginAsync(TUser user, Microsoft.AspNetCore.Identity.UserLoginInfo login) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddPasswordAsync(TUser user, string password) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddToRoleAsync(TUser user, string role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AddToRolesAsync(TUser user, System.Collections.Generic.IEnumerable roles) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ChangeEmailAsync(TUser user, string newEmail, string token) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ChangePhoneNumberAsync(TUser user, string phoneNumber, string token) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CheckPasswordAsync(TUser user, string password) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ConfirmEmailAsync(TUser user, string token) { throw null; } + public virtual System.Threading.Tasks.Task CountRecoveryCodesAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CreateAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CreateAsync(TUser user, string password) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task CreateSecurityTokenAsync(TUser user) { throw null; } + protected virtual string CreateTwoFactorRecoveryCode() { throw null; } + public virtual System.Threading.Tasks.Task DeleteAsync(TUser user) { throw null; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task FindByEmailAsync(string email) { throw null; } + public virtual System.Threading.Tasks.Task FindByIdAsync(string userId) { throw null; } + public virtual System.Threading.Tasks.Task FindByLoginAsync(string loginProvider, string providerKey) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task FindByNameAsync(string userName) { throw null; } + public virtual System.Threading.Tasks.Task GenerateChangeEmailTokenAsync(TUser user, string newEmail) { throw null; } + public virtual System.Threading.Tasks.Task GenerateChangePhoneNumberTokenAsync(TUser user, string phoneNumber) { throw null; } + public virtual System.Threading.Tasks.Task GenerateConcurrencyStampAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task GenerateEmailConfirmationTokenAsync(TUser user) { throw null; } + public virtual string GenerateNewAuthenticatorKey() { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task> GenerateNewTwoFactorRecoveryCodesAsync(TUser user, int number) { throw null; } + public virtual System.Threading.Tasks.Task GeneratePasswordResetTokenAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task GenerateTwoFactorTokenAsync(TUser user, string tokenProvider) { throw null; } + public virtual System.Threading.Tasks.Task GenerateUserTokenAsync(TUser user, string tokenProvider, string purpose) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetAccessFailedCountAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task GetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName) { throw null; } + public virtual System.Threading.Tasks.Task GetAuthenticatorKeyAsync(TUser user) { throw null; } + protected static string GetChangeEmailTokenPurpose(string newEmail) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task> GetClaimsAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetEmailAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetLockoutEnabledAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetLockoutEndDateAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task> GetLoginsAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetPhoneNumberAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task> GetRolesAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetSecurityStampAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetTwoFactorEnabledAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task GetUserAsync(System.Security.Claims.ClaimsPrincipal principal) { throw null; } + public virtual string GetUserId(System.Security.Claims.ClaimsPrincipal principal) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetUserIdAsync(TUser user) { throw null; } + public virtual string GetUserName(System.Security.Claims.ClaimsPrincipal principal) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task GetUserNameAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task> GetUsersForClaimAsync(System.Security.Claims.Claim claim) { throw null; } + public virtual System.Threading.Tasks.Task> GetUsersInRoleAsync(string roleName) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task> GetValidTwoFactorProvidersAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task HasPasswordAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task IsEmailConfirmedAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task IsInRoleAsync(TUser user, string role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task IsLockedOutAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task IsPhoneNumberConfirmedAsync(TUser user) { throw null; } + public virtual string NormalizeEmail(string email) { throw null; } + public virtual string NormalizeName(string name) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RedeemTwoFactorRecoveryCodeAsync(TUser user, string code) { throw null; } + public virtual void RegisterTokenProvider(string providerName, Microsoft.AspNetCore.Identity.IUserTwoFactorTokenProvider provider) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName) { throw null; } + public virtual System.Threading.Tasks.Task RemoveClaimAsync(TUser user, System.Security.Claims.Claim claim) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveClaimsAsync(TUser user, System.Collections.Generic.IEnumerable claims) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveFromRoleAsync(TUser user, string role) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveFromRolesAsync(TUser user, System.Collections.Generic.IEnumerable roles) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task RemovePasswordAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ReplaceClaimAsync(TUser user, System.Security.Claims.Claim claim, System.Security.Claims.Claim newClaim) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ResetAccessFailedCountAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ResetAuthenticatorKeyAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ResetPasswordAsync(TUser user, string token, string newPassword) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName, string tokenValue) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetEmailAsync(TUser user, string email) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetLockoutEnabledAsync(TUser user, bool enabled) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetLockoutEndDateAsync(TUser user, System.DateTimeOffset? lockoutEnd) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetPhoneNumberAsync(TUser user, string phoneNumber) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetTwoFactorEnabledAsync(TUser user, bool enabled) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task SetUserNameAsync(TUser user, string userName) { throw null; } + protected void ThrowIfDisposed() { } + public virtual System.Threading.Tasks.Task UpdateAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task UpdateNormalizedEmailAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task UpdateNormalizedUserNameAsync(TUser user) { throw null; } + protected virtual System.Threading.Tasks.Task UpdatePasswordHash(TUser user, string newPassword, bool validatePassword) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task UpdateSecurityStampAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected virtual System.Threading.Tasks.Task UpdateUserAsync(TUser user) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected System.Threading.Tasks.Task ValidatePasswordAsync(TUser user, string password) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected System.Threading.Tasks.Task ValidateUserAsync(TUser user) { throw null; } + public virtual System.Threading.Tasks.Task VerifyChangePhoneNumberTokenAsync(TUser user, string token, string phoneNumber) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected virtual System.Threading.Tasks.Task VerifyPasswordAsync(Microsoft.AspNetCore.Identity.IUserPasswordStore store, TUser user, string password) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task VerifyTwoFactorTokenAsync(TUser user, string tokenProvider, string token) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token) { throw null; } + } + public partial class UserOptions + { + public UserOptions() { } + public string AllowedUserNameCharacters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RequireUniqueEmail { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class UserValidator : Microsoft.AspNetCore.Identity.IUserValidator where TUser : class + { + public UserValidator(Microsoft.AspNetCore.Identity.IdentityErrorDescriber errors = null) { } + public Microsoft.AspNetCore.Identity.IdentityErrorDescriber Describer { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task ValidateAsync(Microsoft.AspNetCore.Identity.UserManager manager, TUser user) { throw null; } + } +} +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class IdentityServiceCollectionExtensions + { + public static Microsoft.AspNetCore.Identity.IdentityBuilder AddIdentityCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TUser : class { throw null; } + public static Microsoft.AspNetCore.Identity.IdentityBuilder AddIdentityCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) where TUser : class { throw null; } + } +} +namespace System.Security.Claims +{ + public static partial class PrincipalExtensions + { + public static string FindFirstValue(this System.Security.Claims.ClaimsPrincipal principal, string claimType) { throw null; } + } +} diff --git a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj index 526f2bd7e7fe..56f0243273c4 100644 --- a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj +++ b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj @@ -2,8 +2,9 @@ ASP.NET Core Identity is the membership system for building ASP.NET Core web applications, including membership, login, and user data. ASP.NET Core Identity allows you to add login features to your application and makes it easy to customize data about the logged in user. - netcoreapp3.0 + netstandard2.0;netcoreapp3.0 true + true true aspnetcore;identity;membership @@ -12,6 +13,7 @@ + diff --git a/src/Identity/Extensions.Core/src/PasswordHasher.cs b/src/Identity/Extensions.Core/src/PasswordHasher.cs index a9ee0cd6bff4..54c7fd212672 100644 --- a/src/Identity/Extensions.Core/src/PasswordHasher.cs +++ b/src/Identity/Extensions.Core/src/PasswordHasher.cs @@ -65,6 +65,28 @@ public PasswordHasher(IOptions optionsAccessor = null) _rng = options.Rng; } +#if NETSTANDARD2_0 + // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static bool ByteArraysEqual(byte[] a, byte[] b) + { + if (a == null && b == null) + { + return true; + } + if (a == null || b == null || a.Length != b.Length) + { + return false; + } + var areSame = true; + for (var i = 0; i < a.Length; i++) + { + areSame &= (a[i] == b[i]); + } + return areSame; + } +#endif + /// /// Returns a hashed representation of the supplied for the specified . /// @@ -222,7 +244,13 @@ private static bool VerifyHashedPasswordV2(byte[] hashedPassword, string passwor // Hash the incoming password and verify it byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, Pbkdf2Prf, Pbkdf2IterCount, Pbkdf2SubkeyLength); +#if NETSTANDARD2_0 + return ByteArraysEqual(actualSubkey, expectedSubkey); +#elif NETCOREAPP3_0 return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey); +#else +#error Update target frameworks +#endif } private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, out int iterCount) @@ -255,7 +283,13 @@ private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string passwor // Hash the incoming password and verify it byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength); +#if NETSTANDARD2_0 + return ByteArraysEqual(actualSubkey, expectedSubkey); +#elif NETCOREAPP3_0 return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey); +#else +#error Update target frameworks +#endif } catch { diff --git a/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.csproj b/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.csproj index bfb85db1d0fb..77cdf717c7b7 100644 --- a/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.csproj +++ b/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.netcoreapp3.0.cs b/src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.netstandard2.0.cs similarity index 100% rename from src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.netcoreapp3.0.cs rename to src/Identity/Extensions.Stores/ref/Microsoft.Extensions.Identity.Stores.netstandard2.0.cs diff --git a/src/Identity/Extensions.Stores/src/Microsoft.Extensions.Identity.Stores.csproj b/src/Identity/Extensions.Stores/src/Microsoft.Extensions.Identity.Stores.csproj index b51bcb7b27d1..a3fd7e5c6588 100644 --- a/src/Identity/Extensions.Stores/src/Microsoft.Extensions.Identity.Stores.csproj +++ b/src/Identity/Extensions.Stores/src/Microsoft.Extensions.Identity.Stores.csproj @@ -2,8 +2,9 @@ ASP.NET Core Identity is the membership system for building ASP.NET Core web applications, including membership, login, and user data. ASP.NET Core Identity allows you to add login features to your application and makes it easy to customize data about the logged in user. - netcoreapp3.0 + netstandard2.0 true + true true aspnetcore;identity;membership @@ -11,6 +12,7 @@ + diff --git a/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueEmptyOverhead.cs b/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueEmptyOverhead.cs index 5b463efc2bba..b4145022b28c 100644 --- a/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueEmptyOverhead.cs +++ b/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueEmptyOverhead.cs @@ -13,7 +13,8 @@ public class QueueEmptyOverhead { private const int _numRequests = 20000; - private RequestThrottlingMiddleware _middleware; + private RequestThrottlingMiddleware _middlewareFIFO; + private RequestThrottlingMiddleware _middlewareLIFO; private RequestDelegate _restOfServer; [GlobalSetup] @@ -21,9 +22,14 @@ public void GlobalSetup() { _restOfServer = YieldsThreadInternally ? (RequestDelegate)YieldsThread : (RequestDelegate)CompletesImmediately; - _middleware = TestUtils.CreateTestMiddleware_TailDrop( + _middlewareFIFO = TestUtils.CreateTestMiddleware_TailDrop( maxConcurrentRequests: 1, - requestQueueLimit: 0, + requestQueueLimit: 100, + next: _restOfServer); + + _middlewareLIFO = TestUtils.CreateTestMiddleware_StackPolicy( + maxConcurrentRequests: 1, + requestQueueLimit: 100, next: _restOfServer); } @@ -40,11 +46,20 @@ public async Task Baseline() } [Benchmark(OperationsPerInvoke = _numRequests)] - public async Task WithEmptyQueueOverhead() + public async Task WithEmptyQueueOverhead_FIFO() + { + for (int i = 0; i < _numRequests; i++) + { + await _middlewareFIFO.Invoke(null); + } + } + + [Benchmark(OperationsPerInvoke = _numRequests)] + public async Task WithEmptyQueueOverhead_LIFO() { for (int i = 0; i < _numRequests; i++) { - await _middleware.Invoke(null); + await _middlewareLIFO.Invoke(null); } } diff --git a/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueFullOverhead.cs b/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueFullOverhead.cs index e9b4995d6c6b..2cdfdc1f8330 100644 --- a/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueFullOverhead.cs +++ b/src/Middleware/RequestThrottling/perf/Microbenchmarks/QueueFullOverhead.cs @@ -11,11 +11,12 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks { public class QueueFullOverhead { - private const int _numRequests = 2000; + private const int _numRequests = 200; private int _requestCount = 0; private ManualResetEventSlim _mres = new ManualResetEventSlim(); - private RequestThrottlingMiddleware _middleware; + private RequestThrottlingMiddleware _middleware_FIFO; + private RequestThrottlingMiddleware _middleware_LIFO; [Params(8)] public int MaxConcurrentRequests; @@ -23,11 +24,15 @@ public class QueueFullOverhead [GlobalSetup] public void GlobalSetup() { - _middleware = TestUtils.CreateTestMiddleware_TailDrop( + _middleware_FIFO = TestUtils.CreateTestMiddleware_TailDrop( maxConcurrentRequests: MaxConcurrentRequests, requestQueueLimit: _numRequests, - next: IncrementAndCheck - ); + next: IncrementAndCheck); + + _middleware_LIFO = TestUtils.CreateTestMiddleware_StackPolicy( + maxConcurrentRequests: MaxConcurrentRequests, + requestQueueLimit: _numRequests, + next: IncrementAndCheck); } [IterationSetup] @@ -59,14 +64,26 @@ public void Baseline() } [Benchmark(OperationsPerInvoke = _numRequests)] - public void QueueingAll() + public void QueueingAll_FIFO() { for (int i = 0; i < _numRequests; i++) { - _ = _middleware.Invoke(null); + _ = _middleware_FIFO.Invoke(null); } _mres.Wait(); } + + [Benchmark(OperationsPerInvoke = _numRequests)] + public void QueueingAll_LIFO() + { + for (int i = 0; i < _numRequests; i++) + { + _ = _middleware_LIFO.Invoke(null); + } + + _mres.Wait(); + } + } } diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs index 552bed23e292..7fc7d24be7e7 100644 --- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs +++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs @@ -28,11 +28,11 @@ public RequestThrottlingOptions() { } public Microsoft.AspNetCore.Http.RequestDelegate OnRejected { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } } -namespace Microsoft.AspNetCore.RequestThrottling.Policies +namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies { - public partial class TailDropOptions + public partial class QueuePolicyOptions { - public TailDropOptions() { } + public QueuePolicyOptions() { } public int MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public int RequestQueueLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } @@ -41,6 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class QueuePolicyServiceCollectionExtensions { - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddTailDropQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddStackQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddTailDropQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } } } diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj index 4b8a97aae2ac..3262b0749b8b 100644 --- a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj +++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs index 80751b512a09..d12dcfa943de 100644 --- a/src/Middleware/RequestThrottling/sample/Startup.cs +++ b/src/Middleware/RequestThrottling/sample/Startup.cs @@ -1,11 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -14,14 +16,16 @@ namespace RequestThrottlingSample { public class Startup { + static IConfiguration _config; + // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddTailDropQueue((options) => { - options.MaxConcurrentRequests = 4; - options.RequestQueueLimit = 0; + options.MaxConcurrentRequests = Math.Max(1, _config.GetValue("maxCores")); + options.RequestQueueLimit = Math.Max(1, _config.GetValue("maxQueue")); }); services.AddLogging(); @@ -30,17 +34,21 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseRequestThrottling(); - app.Run(async context => { - await context.Response.WriteAsync("Hello Request Throttling! If you refresh this page a bunch, it will 503."); - await Task.Delay(1000); + await context.Response.WriteAsync("Hello Request Throttling! If you rapidly refresh this page, it will 503."); + await Task.Delay(400); }); } // Entry point for the application. public static void Main(string[] args) { + _config = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .AddCommandLine(args) + .Build(); + var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file diff --git a/src/Middleware/RequestThrottling/src/Policies/QueuePolicyServiceCollectionExtensions.cs b/src/Middleware/RequestThrottling/src/Policies/QueuePolicyServiceCollectionExtensions.cs deleted file mode 100644 index d872cdb5bbcc..000000000000 --- a/src/Middleware/RequestThrottling/src/Policies/QueuePolicyServiceCollectionExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; -using System.Xml.Schema; -using Microsoft.AspNetCore.RequestThrottling; -using Microsoft.AspNetCore.RequestThrottling.Policies; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Contains methods for adding Q - /// - public static class QueuePolicyServiceCollectionExtensions - { - /// - /// Tells to use a TailDrop queue as its queueing strategy. - /// - /// The to add services to. - /// Set the options used by the queue. - /// Mandatory, since must be provided. - /// - public static IServiceCollection AddTailDropQueue(this IServiceCollection services, Action configure) - { - services.Configure(configure); - services.AddSingleton(); - return services; - } - } -} diff --git a/src/Middleware/RequestThrottling/src/IQueuePolicy.cs b/src/Middleware/RequestThrottling/src/QueuePolicies/IQueuePolicy.cs similarity index 100% rename from src/Middleware/RequestThrottling/src/IQueuePolicy.cs rename to src/Middleware/RequestThrottling/src/QueuePolicies/IQueuePolicy.cs diff --git a/src/Middleware/RequestThrottling/src/Policies/TailDropOptions.cs b/src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyOptions.cs similarity index 81% rename from src/Middleware/RequestThrottling/src/Policies/TailDropOptions.cs rename to src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyOptions.cs index 7c405d20aa0e..1d8e284d2e16 100644 --- a/src/Middleware/RequestThrottling/src/Policies/TailDropOptions.cs +++ b/src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyOptions.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.AspNetCore.RequestThrottling.Policies +namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies { /// - /// Specifies options for the + /// Specifies options for the /// - public class TailDropOptions + public class QueuePolicyOptions { /// /// Maximum number of concurrent requests. Any extras will be queued on the server. diff --git a/src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs b/src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs new file mode 100644 index 000000000000..911220d983e7 --- /dev/null +++ b/src/Middleware/RequestThrottling/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.RequestThrottling; +using Microsoft.AspNetCore.RequestThrottling.QueuePolicies; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Contains methods for specifying which queue the middleware should use. + /// + public static class QueuePolicyServiceCollectionExtensions + { + /// + /// Tells to use a FIFO queue as its queueing strategy. + /// + /// The to add services to. + /// Set the options used by the queue. + /// Mandatory, since must be provided. + /// + public static IServiceCollection AddTailDropQueue(this IServiceCollection services, Action configure) + { + services.Configure(configure); + services.AddSingleton(); + return services; + } + + /// + /// Tells to use a LIFO stack as its queueing strategy. + /// + /// The to add services to. + /// Set the options used by the queue. + /// Mandatory, since must be provided. + /// + public static IServiceCollection AddStackQueue(this IServiceCollection services, Action configure) + { + services.Configure(configure); + services.AddSingleton(); + return services; + } + } +} diff --git a/src/Middleware/RequestThrottling/src/QueuePolicies/StackQueuePolicy.cs b/src/Middleware/RequestThrottling/src/QueuePolicies/StackQueuePolicy.cs new file mode 100644 index 000000000000..5fd91560c0fc --- /dev/null +++ b/src/Middleware/RequestThrottling/src/QueuePolicies/StackQueuePolicy.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies +{ + internal class StackQueuePolicy : IQueuePolicy + { + private readonly List> _buffer; + private readonly int _maxQueueCapacity; + private readonly int _maxConcurrentRequests; + private bool _hasReachedCapacity; + private int _head; + private int _queueLength; + + private static readonly Task _trueTask = Task.FromResult(true); + + private readonly object _bufferLock = new Object(); + + private int _freeServerSpots; + + public StackQueuePolicy(IOptions options) + { + _buffer = new List>(); + _maxQueueCapacity = options.Value.RequestQueueLimit; + _maxConcurrentRequests = options.Value.MaxConcurrentRequests; + _freeServerSpots = options.Value.MaxConcurrentRequests; + } + + public Task TryEnterAsync() + { + lock (_bufferLock) + { + if (_freeServerSpots > 0) + { + _freeServerSpots--; + return _trueTask; + } + + // if queue is full, cancel oldest request + if (_queueLength == _maxQueueCapacity) + { + _hasReachedCapacity = true; + _buffer[_head].SetResult(false); + _queueLength--; + } + + // enqueue request with a tcs + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + if (_hasReachedCapacity || _queueLength < _buffer.Count) + { + _buffer[_head] = tcs; + } + else + { + _buffer.Add(tcs); + } + _queueLength++; + + // increment _head for next time + _head++; + if (_head == _maxQueueCapacity) + { + _head = 0; + } + return tcs.Task; + } + } + + public void OnExit() + { + lock (_bufferLock) + { + if (_queueLength == 0) + { + _freeServerSpots++; + + if (_freeServerSpots > _maxConcurrentRequests) + { + _freeServerSpots--; + throw new InvalidOperationException("OnExit must only be called once per successful call to TryEnterAsync"); + } + + return; + } + + // step backwards and launch a new task + if (_head == 0) + { + _head = _maxQueueCapacity - 1; + } + else + { + _head--; + } + + _buffer[_head].SetResult(true); + _buffer[_head] = null; + _queueLength--; + } + } + } +} diff --git a/src/Middleware/RequestThrottling/src/Policies/TailDrop.cs b/src/Middleware/RequestThrottling/src/QueuePolicies/TailDropQueuePolicy.cs similarity index 91% rename from src/Middleware/RequestThrottling/src/Policies/TailDrop.cs rename to src/Middleware/RequestThrottling/src/QueuePolicies/TailDropQueuePolicy.cs index c87ab1b4cb04..a0bd6675318d 100644 --- a/src/Middleware/RequestThrottling/src/Policies/TailDrop.cs +++ b/src/Middleware/RequestThrottling/src/QueuePolicies/TailDropQueuePolicy.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.RequestThrottling.Policies +namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies { - internal class TailDrop : IQueuePolicy, IDisposable + internal class TailDropQueuePolicy : IQueuePolicy, IDisposable { private readonly int _maxConcurrentRequests; private readonly int _requestQueueLimit; @@ -17,7 +17,7 @@ internal class TailDrop : IQueuePolicy, IDisposable private object _totalRequestsLock = new object(); public int TotalRequests { get; private set; } - public TailDrop(IOptions options) + public TailDropQueuePolicy(IOptions options) { _maxConcurrentRequests = options.Value.MaxConcurrentRequests; if (_maxConcurrentRequests <= 0) diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs index a1d09f24aa47..14e55a33e6b8 100644 --- a/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs +++ b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using System.Xml.Schema; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/src/Middleware/RequestThrottling/test/PolicyTests/StackQueueTests.cs b/src/Middleware/RequestThrottling/test/PolicyTests/StackQueueTests.cs new file mode 100644 index 000000000000..aaf34bcf9b94 --- /dev/null +++ b/src/Middleware/RequestThrottling/test/PolicyTests/StackQueueTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.RequestThrottling.QueuePolicies; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.RequestThrottling.Tests.PolicyTests +{ + public static class StackQueueTests + { + [Fact] + public static void BaseFunctionality() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions { + MaxConcurrentRequests = 0, + RequestQueueLimit = 2, + })); + + var task1 = stack.TryEnterAsync(); + + Assert.False(task1.IsCompleted); + + stack.OnExit(); + + Assert.True(task1.IsCompleted && task1.Result); + } + + [Fact] + public static void OldestRequestOverwritten() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions { + MaxConcurrentRequests = 0, + RequestQueueLimit = 3, + })); + + var task1 = stack.TryEnterAsync(); + Assert.False(task1.IsCompleted); + var task2 = stack.TryEnterAsync(); + Assert.False(task2.IsCompleted); + var task3 = stack.TryEnterAsync(); + Assert.False(task3.IsCompleted); + + var task4 = stack.TryEnterAsync(); + Assert.False(task4.IsCompleted); + + Assert.True(task1.IsCompleted); + Assert.False(task1.Result); + } + + [Fact] + public static void RespectsMaxConcurrency() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions { + MaxConcurrentRequests = 2, + RequestQueueLimit = 2, + })); + + var task1 = stack.TryEnterAsync(); + Assert.True(task1.IsCompleted); + + var task2 = stack.TryEnterAsync(); + Assert.True(task2.IsCompleted); + + var task3 = stack.TryEnterAsync(); + Assert.False(task3.IsCompleted); + } + + [Fact] + public static void ExitRequestsPreserveSemaphoreState() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions { + MaxConcurrentRequests = 1, + RequestQueueLimit = 2, + })); + + var task1 = stack.TryEnterAsync(); + Assert.True(task1.IsCompleted && task1.Result); + + var task2 = stack.TryEnterAsync(); + Assert.False(task2.IsCompleted); + + stack.OnExit(); // t1 exits, should free t2 to return + Assert.True(task2.IsCompleted && task2.Result); + + stack.OnExit(); // t2 exists, there's now a free spot in server + + var task3 = stack.TryEnterAsync(); + Assert.True(task3.IsCompleted && task3.Result); + } + + [Fact] + public static void StaleRequestsAreProperlyOverwritten() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions + { + MaxConcurrentRequests = 0, + RequestQueueLimit = 4, + })); + + var task1 = stack.TryEnterAsync(); + stack.OnExit(); + Assert.True(task1.IsCompleted); + + var task2 = stack.TryEnterAsync(); + stack.OnExit(); + Assert.True(task2.IsCompleted); + } + + [Fact] + public static async Task OneTryEnterAsyncOneOnExit() + { + var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions + { + MaxConcurrentRequests = 1, + RequestQueueLimit = 4, + })); + + Assert.Throws(() => stack.OnExit()); + + await stack.TryEnterAsync(); + + stack.OnExit(); + + Assert.Throws(() => stack.OnExit()); + } + } +} diff --git a/src/Middleware/RequestThrottling/test/RequestQueueTests.cs b/src/Middleware/RequestThrottling/test/PolicyTests/TailDropTests.cs similarity index 100% rename from src/Middleware/RequestThrottling/test/RequestQueueTests.cs rename to src/Middleware/RequestThrottling/test/PolicyTests/TailDropTests.cs diff --git a/src/Middleware/RequestThrottling/test/TestUtils.cs b/src/Middleware/RequestThrottling/test/TestUtils.cs index 88199369ad72..4617914294f0 100644 --- a/src/Middleware/RequestThrottling/test/TestUtils.cs +++ b/src/Middleware/RequestThrottling/test/TestUtils.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.RequestThrottling.Policies; +using Microsoft.AspNetCore.RequestThrottling.QueuePolicies; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -37,15 +37,35 @@ public static RequestThrottlingMiddleware CreateTestMiddleware_TailDrop(int maxC ); } - internal static TailDrop CreateTailDropQueue(int maxConcurrentRequests, int requestQueueLimit = 5000) + public static RequestThrottlingMiddleware CreateTestMiddleware_StackPolicy(int maxConcurrentRequests, int requestQueueLimit, RequestDelegate onRejected = null, RequestDelegate next = null) { - var options = Options.Create(new TailDropOptions + return CreateTestMiddleware( + queue: CreateStackPolicy(maxConcurrentRequests, requestQueueLimit), + onRejected: onRejected, + next: next + ); + } + + internal static StackQueuePolicy CreateStackPolicy(int maxConcurrentRequests, int requestsQueuelimit = 100) + { + var options = Options.Create(new QueuePolicyOptions + { + MaxConcurrentRequests = maxConcurrentRequests, + RequestQueueLimit = requestsQueuelimit + }); + + return new StackQueuePolicy(options); + } + + internal static TailDropQueuePolicy CreateTailDropQueue(int maxConcurrentRequests, int requestQueueLimit = 100) + { + var options = Options.Create(new QueuePolicyOptions { MaxConcurrentRequests = maxConcurrentRequests, RequestQueueLimit = requestQueueLimit }); - return new TailDrop(options); + return new TailDropQueuePolicy(options); } } diff --git a/src/ProjectTemplates/README.md b/src/ProjectTemplates/README.md index 59f1617100c1..ce53e760e3b1 100644 --- a/src/ProjectTemplates/README.md +++ b/src/ProjectTemplates/README.md @@ -15,8 +15,9 @@ Some projects in this repository (like SignalR Java Client) require JDK installa 1. Run `git submodule update --init --recursive` if you haven't already. 1. Run `git submodule update` to update submodules. 1. Run `build.cmd -all -pack` in the repository root to build all of the dependencies. -1. Run `build.cmd` in this directory will produce NuGet packages for each class of template in the artifacts directory. +1. Run `build.cmd -pack -NoRestore -NoBuilddeps` in this directory will produce NuGet packages for each class of template in the artifacts directory. 1. Because the templates build against the version of `Microsoft.AspNetCore.App` that was built during the previous step, it is NOT advised that you install templates created on your local machine via `dotnet new -i [nupkgPath]`. Instead, use the `Run-[Template]-Locally.ps1` scripts in the script folder. These scripts do `dotnet new -i` with your packages, but also apply a series of fixes and tweaks to the created template which keep the fact that you don't have a production `Microsoft.AspNetCore.App` from interfering. 1. The ASP.NET localhost development certificate must also be installed and trusted or else you'll get a test error "Certificate error: Navigation blocked". +1. Run `build.cmd -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true"` to run template tests. ** Note** Templating tests require Visual Studio unless a full build (CI) is performed. diff --git a/src/ProjectTemplates/build.sh b/src/ProjectTemplates/build.sh new file mode 100755 index 000000000000..7046bb98a0fc --- /dev/null +++ b/src/ProjectTemplates/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +repo_root="$DIR/../.." +"$repo_root/build.sh" --projects "$DIR/**/*.*proj" "$@" diff --git a/src/Security/samples/Identity.ExternalClaims/Startup.cs b/src/Security/samples/Identity.ExternalClaims/Startup.cs index 68ea7bab3cb5..e1f88216699c 100644 --- a/src/Security/samples/Identity.ExternalClaims/Startup.cs +++ b/src/Security/samples/Identity.ExternalClaims/Startup.cs @@ -81,8 +81,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseStaticFiles(); - app.UseAuthentication(); - app.UseRouting(); app.UseAuthentication(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs index eead5acb9975..701931e85a31 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs @@ -148,6 +148,7 @@ public async Task Server_AppException_ClientReset() } [ConditionalFact] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2415", FlakyOn.All)] public void Server_MultipleOutstandingSyncRequests_Success() { int requestLimit = 10; diff --git a/src/Servers/IIS/IIS/src/Core/OutputProducer.cs b/src/Servers/IIS/IIS/src/Core/OutputProducer.cs index 431f5c809aea..2dee24f7b971 100644 --- a/src/Servers/IIS/IIS/src/Core/OutputProducer.cs +++ b/src/Servers/IIS/IIS/src/Core/OutputProducer.cs @@ -13,25 +13,22 @@ namespace Microsoft.AspNetCore.Server.IIS.Core { internal class OutputProducer { - // This locks access to to all of the below fields + // This locks access to _completed. private readonly object _contextLock = new object(); - - private ValueTask _flushTask; private bool _completed = false; private readonly Pipe _pipe; // https://github.com/dotnet/corefxlab/issues/1334 - // Pipelines don't support multiple awaiters on flush - // this is temporary until it does - private TaskCompletionSource _flushTcs; + // https://github.com/aspnet/AspNetCore/issues/8843 + // Pipelines don't support multiple awaiters on flush. This is temporary until it does. + // _lastFlushTask field should only be get or set under the _flushLock. private readonly object _flushLock = new object(); - private Action _flushCompleted; + private Task _lastFlushTask = Task.CompletedTask; public OutputProducer(Pipe pipe) { _pipe = pipe; - _flushCompleted = OnFlushCompleted; } public PipeReader Reader => _pipe.Reader; @@ -90,35 +87,27 @@ public Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellati private Task FlushAsync(PipeWriter pipeWriter, CancellationToken cancellationToken) { - var awaitable = pipeWriter.FlushAsync(cancellationToken); - if (awaitable.IsCompleted) + lock (_flushLock) { - // The flush task can't fail today - return Task.CompletedTask; + _lastFlushTask = _lastFlushTask.IsCompleted ? + FlushNowAsync(pipeWriter, cancellationToken) : + AwaitLastFlushAndThenFlushAsync(_lastFlushTask, pipeWriter, cancellationToken); + + return _lastFlushTask; } - return FlushAsyncAwaited(awaitable, cancellationToken); } - private async Task FlushAsyncAwaited(ValueTask awaitable, CancellationToken cancellationToken) + private Task FlushNowAsync(PipeWriter pipeWriter, CancellationToken cancellationToken) { - // https://github.com/dotnet/corefxlab/issues/1334 - // Since the flush awaitable doesn't currently support multiple awaiters - // we need to use a task to track the callbacks. - // All awaiters get the same task - lock (_flushLock) - { - _flushTask = awaitable; - if (_flushTcs == null || _flushTcs.Task.IsCompleted) - { - _flushTcs = new TaskCompletionSource(); - - _flushTask.GetAwaiter().OnCompleted(_flushCompleted); - } - } + var awaitable = pipeWriter.FlushAsync(cancellationToken); + return awaitable.IsCompleted ? Task.CompletedTask : FlushNowAsyncAwaited(awaitable, cancellationToken); + } + private async Task FlushNowAsyncAwaited(ValueTask awaitable, CancellationToken cancellationToken) + { try { - await _flushTcs.Task; + await awaitable; cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException ex) @@ -132,21 +121,10 @@ private async Task FlushAsyncAwaited(ValueTask awaitable, Cancellat } } - private void OnFlushCompleted() + private async Task AwaitLastFlushAndThenFlushAsync(Task lastFlushTask, PipeWriter pipeWriter, CancellationToken cancellationToken) { - try - { - _flushTask.GetAwaiter().GetResult(); - _flushTcs.TrySetResult(null); - } - catch (Exception exception) - { - _flushTcs.TrySetResult(exception); - } - finally - { - _flushTask = default; - } + await lastFlushTask; + await FlushNowAsync(pipeWriter, cancellationToken); } } } diff --git a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj index e72a107035ab..15d554412279 100644 --- a/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj +++ b/src/Servers/IIS/IIS/src/Microsoft.AspNetCore.Server.IIS.csproj @@ -55,6 +55,7 @@ diff --git a/src/Servers/IIS/IISIntegration/src/AuthenticationHandler.cs b/src/Servers/IIS/IISIntegration/src/AuthenticationHandler.cs index 1a660b490f2d..075705000a3e 100644 --- a/src/Servers/IIS/IISIntegration/src/AuthenticationHandler.cs +++ b/src/Servers/IIS/IISIntegration/src/AuthenticationHandler.cs @@ -1,32 +1,24 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Security.Claims; -using System.Globalization; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.IISIntegration { internal class AuthenticationHandler : IAuthenticationHandler { - private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; - private static readonly Func ClearUserDelegate = ClearUser; private WindowsPrincipal _user; private HttpContext _context; - - internal AuthenticationScheme Scheme { get; private set; } + private AuthenticationScheme _scheme; public Task AuthenticateAsync() { - var user = GetUser(); - if (user != null) + if (_user != null) { - return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name))); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(_user, _scheme.Name))); } else { @@ -34,44 +26,6 @@ public Task AuthenticateAsync() } } - private WindowsPrincipal GetUser() - { - if (_user == null) - { - var tokenHeader = _context.Request.Headers[MSAspNetCoreWinAuthToken]; - - int hexHandle; - if (!StringValues.IsNullOrEmpty(tokenHeader) - && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle)) - { - // Always create the identity if the handle exists, we need to dispose it so it does not leak. - var handle = new IntPtr(hexHandle); - var winIdentity = new WindowsIdentity(handle, IISDefaults.AuthenticationScheme); - - // WindowsIdentity just duplicated the handle so we need to close the original. - NativeMethods.CloseHandle(handle); - - _context.Response.RegisterForDispose(winIdentity); - // We don't want loggers accessing a disposed identity. - // https://github.com/aspnet/Logging/issues/543#issuecomment-321907828 - _context.Response.OnCompleted(ClearUserDelegate, _context); - _user = new WindowsPrincipal(winIdentity); - } - } - - return _user; - } - - private static Task ClearUser(object arg) - { - var context = (HttpContext)arg; - if (context.User is WindowsPrincipal) - { - context.User = null; - } - return Task.CompletedTask; - } - public Task ChallengeAsync(AuthenticationProperties properties) { // We would normally set the www-authenticate header here, but IIS does that for us. @@ -87,8 +41,9 @@ public Task ForbidAsync(AuthenticationProperties properties) public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { - Scheme = scheme; + _scheme = scheme; _context = context; + _user = context.Features.Get(); // See IISMiddleware return Task.CompletedTask; } } diff --git a/src/Servers/IIS/IISIntegration/src/IISMiddleware.cs b/src/Servers/IIS/IISIntegration/src/IISMiddleware.cs index 77a4211b7462..f0596d40ccc6 100644 --- a/src/Servers/IIS/IISIntegration/src/IISMiddleware.cs +++ b/src/Servers/IIS/IISIntegration/src/IISMiddleware.cs @@ -3,10 +3,11 @@ using System; using System.Diagnostics; +using System.Globalization; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Hosting; @@ -21,8 +22,10 @@ public class IISMiddleware private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT"; private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN"; private const string MSAspNetCoreEvent = "MS-ASPNETCORE-EVENT"; + private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; private const string ANCMShutdownEventHeaderValue = "shutdown"; private static readonly PathString ANCMRequestPath = new PathString("/iisintegration"); + private static readonly Func ClearUserDelegate = ClearUser; private readonly RequestDelegate _next; private readonly IISOptions _options; @@ -131,10 +134,16 @@ public async Task Invoke(HttpContext httpContext) if (_options.ForwardWindowsAuthentication) { // We must always process and clean up the windows identity, even if we don't assign the User. - var result = await httpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme); - if (result.Succeeded && _options.AutomaticAuthentication) + var user = GetUser(httpContext); + if (user != null) { - httpContext.User = result.Principal; + // Flow it through to the authentication handler. + httpContext.Features.Set(user); + + if (_options.AutomaticAuthentication) + { + httpContext.User = user; + } } } @@ -147,5 +156,39 @@ public async Task Invoke(HttpContext httpContext) await _next(httpContext); } + + private WindowsPrincipal GetUser(HttpContext context) + { + var tokenHeader = context.Request.Headers[MSAspNetCoreWinAuthToken]; + + if (!StringValues.IsNullOrEmpty(tokenHeader) + && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hexHandle)) + { + // Always create the identity if the handle exists, we need to dispose it so it does not leak. + var handle = new IntPtr(hexHandle); + var winIdentity = new WindowsIdentity(handle, IISDefaults.AuthenticationScheme); + + // WindowsIdentity just duplicated the handle so we need to close the original. + NativeMethods.CloseHandle(handle); + + context.Response.OnCompleted(ClearUserDelegate, context); + context.Response.RegisterForDispose(winIdentity); + return new WindowsPrincipal(winIdentity); + } + + return null; + } + + private static Task ClearUser(object arg) + { + var context = (HttpContext)arg; + // We don't want loggers accessing a disposed identity. + // https://github.com/aspnet/Logging/issues/543#issuecomment-321907828 + if (context.User is WindowsPrincipal) + { + context.User = null; + } + return Task.CompletedTask; + } } } diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs index c1bbe28155ae..64277f5f9d03 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs @@ -143,7 +143,6 @@ public partial class ListenOptions : Microsoft.AspNetCore.Connections.IConnectio { internal ListenOptions() { } public System.IServiceProvider ApplicationServices { get { throw null; } } - public System.Collections.Generic.List ConnectionAdapters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public ulong FileHandle { get { throw null; } } public System.Net.IPEndPoint IPEndPoint { get { throw null; } } public Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions KestrelServerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } @@ -160,24 +159,6 @@ public MinDataRate(double bytesPerSecond, System.TimeSpan gracePeriod) { } public System.TimeSpan GracePeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } } -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - public partial class ConnectionAdapterContext - { - internal ConnectionAdapterContext() { } - public System.IO.Stream ConnectionStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } - } - public partial interface IAdaptedConnection : System.IDisposable - { - System.IO.Stream ConnectionStream { get; } - } - public partial interface IConnectionAdapter - { - bool IsHttps { get; } - System.Threading.Tasks.Task OnConnectionAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal.ConnectionAdapterContext context); - } -} namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features { public partial interface IConnectionTimeoutFeature @@ -301,6 +282,7 @@ public HttpsConnectionAdapterOptions() { } public System.Security.Cryptography.X509Certificates.X509Certificate2 ServerCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func ServerCertificateSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Security.Authentication.SslProtocols SslProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public void AllowAnyClientCertificate() { } } } namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs deleted file mode 100644 index 337f11135f76..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/AdaptedPipeline.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.IO.Pipelines; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - internal class AdaptedPipeline : IDuplexPipe - { - private readonly int _minAllocBufferSize; - - private Task _inputTask; - private Task _outputTask; - - public AdaptedPipeline(IDuplexPipe transport, - Pipe inputPipe, - Pipe outputPipe, - IKestrelTrace log, - int minAllocBufferSize) - { - TransportStream = new RawStream(transport.Input, transport.Output, throwOnCancelled: true); - Input = inputPipe; - Output = outputPipe; - Log = log; - _minAllocBufferSize = minAllocBufferSize; - } - - public RawStream TransportStream { get; } - - public Pipe Input { get; } - - public Pipe Output { get; } - - public IKestrelTrace Log { get; } - - PipeReader IDuplexPipe.Input => Input.Reader; - - PipeWriter IDuplexPipe.Output => Output.Writer; - - public void RunAsync(Stream stream) - { - _inputTask = ReadInputAsync(stream); - _outputTask = WriteOutputAsync(stream); - } - - public async Task CompleteAsync() - { - Output.Writer.Complete(); - Input.Reader.Complete(); - - if (_outputTask == null) - { - return; - } - - // Wait for the output task to complete, this ensures that we've copied - // the application data to the underlying stream - await _outputTask; - - // Cancel the underlying stream so that the input task yields - TransportStream.CancelPendingRead(); - - // The input task should yield now that we've cancelled it - await _inputTask; - } - - private async Task WriteOutputAsync(Stream stream) - { - try - { - if (stream == null) - { - return; - } - - while (true) - { - var result = await Output.Reader.ReadAsync(); - var buffer = result.Buffer; - - try - { - if (buffer.IsEmpty) - { - if (result.IsCompleted) - { - break; - } - await stream.FlushAsync(); - } - else if (buffer.IsSingleSegment) - { - await stream.WriteAsync(buffer.First); - } - else - { - foreach (var memory in buffer) - { - await stream.WriteAsync(memory); - } - } - } - finally - { - Output.Reader.AdvanceTo(buffer.End); - } - } - } - catch (Exception ex) - { - Log.LogError(0, ex, $"{nameof(AdaptedPipeline)}.{nameof(WriteOutputAsync)}"); - } - finally - { - Output.Reader.Complete(); - } - } - - private async Task ReadInputAsync(Stream stream) - { - Exception error = null; - - try - { - if (stream == null) - { - // REVIEW: Do we need an exception here? - return; - } - - while (true) - { - var outputBuffer = Input.Writer.GetMemory(_minAllocBufferSize); - var bytesRead = await stream.ReadAsync(outputBuffer); - Input.Writer.Advance(bytesRead); - - if (bytesRead == 0) - { - // FIN - break; - } - - var result = await Input.Writer.FlushAsync(); - - if (result.IsCompleted) - { - break; - } - } - } - catch (OperationCanceledException ex) - { - // Propagate the exception if it's ConnectionAbortedException - error = ex as ConnectionAbortedException; - } - catch (Exception ex) - { - // Don't rethrow the exception. It should be handled by the Pipeline consumer. - error = ex; - } - finally - { - Input.Writer.Complete(error); - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs deleted file mode 100644 index 3896e1cf85c4..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/ConnectionAdapterContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Http.Features; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - // Even though this only includes the non-adapted ConnectionStream currently, this is a context in case - // we want to add more connection metadata later. - public class ConnectionAdapterContext - { - internal ConnectionAdapterContext(ConnectionContext connectionContext, Stream connectionStream) - { - ConnectionContext = connectionContext; - ConnectionStream = connectionStream; - } - - internal ConnectionContext ConnectionContext { get; } - - public IFeatureCollection Features => ConnectionContext.Features; - - public Stream ConnectionStream { get; } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs deleted file mode 100644 index 5960490e2bbb..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/IAdaptedConnection.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - public interface IAdaptedConnection : IDisposable - { - Stream ConnectionStream { get; } - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs deleted file mode 100644 index e0249d55456a..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/IConnectionAdapter.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - public interface IConnectionAdapter - { - bool IsHttps { get; } - Task OnConnectionAsync(ConnectionAdapterContext context); - } -} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs deleted file mode 100644 index c69aca5d62da..000000000000 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingConnectionAdapter.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal -{ - internal class LoggingConnectionAdapter : IConnectionAdapter - { - private readonly ILogger _logger; - - public LoggingConnectionAdapter(ILogger logger) - { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - } - - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - return Task.FromResult( - new LoggingAdaptedConnection(context.ConnectionStream, _logger)); - } - - private class LoggingAdaptedConnection : IAdaptedConnection - { - public LoggingAdaptedConnection(Stream rawStream, ILogger logger) - { - ConnectionStream = new LoggingStream(rawStream, logger); - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs index baf7785773ce..92e80cc8d098 100644 --- a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs +++ b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs @@ -55,7 +55,8 @@ public HttpsConnectionAdapterOptions() public ClientCertificateMode ClientCertificateMode { get; set; } /// - /// Specifies a callback for additional client certificate validation that will be invoked during authentication. + /// Specifies a callback for additional client certificate validation that will be invoked during authentication. This will be ignored + /// if is called after this callback is set. /// public Func ClientCertificateValidation { get; set; } @@ -75,6 +76,14 @@ public HttpsConnectionAdapterOptions() /// public bool CheckCertificateRevocation { get; set; } + /// + /// Overrides the current callback and allows any client certificate. + /// + public void AllowAnyClientCertificate() + { + ClientCertificateValidation = (_, __, ___) => true; + } + /// /// Provides direct configuration of the on a per-connection basis. /// This is called after all of the other settings have already been applied. diff --git a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs index 83520f36598d..70a306c3adda 100644 --- a/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs +++ b/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs @@ -171,8 +171,7 @@ public async Task BindAsync(AddressBindContext context) var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out https); context.ServerOptions.ApplyEndpointDefaults(httpsDefault); - if (httpsDefault.ConnectionAdapters.Any(f => f.IsHttps) - || httpsDefault.TryUseHttps()) + if (httpsDefault.IsTls || httpsDefault.TryUseHttps()) { await httpsDefault.BindAsync(context).ConfigureAwait(false); context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses, @@ -255,7 +254,7 @@ public virtual async Task BindAsync(AddressBindContext context) var options = ParseAddress(address, out var https); context.ServerOptions.ApplyEndpointDefaults(options); - if (https && !options.ConnectionAdapters.Any(f => f.IsHttps)) + if (https && !options.IsTls) { options.UseHttps(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs index 4e354d811358..8670469ea5f8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConnectionDispatcher.cs @@ -86,7 +86,7 @@ internal async Task Execute(KestrelConnection connection) } catch (Exception ex) { - Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); + Log.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 6d25e2b14109..0022748f34e3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -2,10 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.IO.Pipelines; using System.Net; using System.Threading.Tasks; @@ -13,7 +10,6 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; @@ -30,9 +26,6 @@ internal class HttpConnection : ITimeoutHandler private readonly ISystemClock _systemClock; private readonly TimeoutControl _timeoutControl; - private IList _adaptedConnections; - private IDuplexPipe _adaptedTransport; - private readonly object _protocolSelectionLock = new object(); private ProtocolSelectionState _protocolSelectionState = ProtocolSelectionState.Initializing; private IRequestProcessor _requestProcessor; @@ -50,54 +43,12 @@ public HttpConnection(HttpConnectionContext context) public IPEndPoint LocalEndPoint => _context.LocalEndPoint; public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; - private MemoryPool MemoryPool => _context.MemoryPool; - - // Internal for testing - internal PipeOptions AdaptedInputPipeOptions => new PipeOptions - ( - pool: MemoryPool, - readerScheduler: _context.ServiceContext.Scheduler, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() - ); - - internal PipeOptions AdaptedOutputPipeOptions => new PipeOptions - ( - pool: MemoryPool, - readerScheduler: PipeScheduler.Inline, - writerScheduler: PipeScheduler.Inline, - pauseWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, - resumeWriterThreshold: _context.ServiceContext.ServerOptions.Limits.MaxResponseBufferSize ?? 0, - useSynchronizationContext: false, - minimumSegmentSize: MemoryPool.GetMinimumSegmentSize() - ); - private IKestrelTrace Log => _context.ServiceContext.Log; public async Task ProcessRequestsAsync(IHttpApplication httpApplication) { try { - AdaptedPipeline adaptedPipeline = null; - - // _adaptedTransport must be set prior to wiring up callbacks - // to allow the connection to be aborted prior to protocol selection. - _adaptedTransport = _context.Transport; - - if (_context.ConnectionAdapters.Count > 0) - { - adaptedPipeline = new AdaptedPipeline(_adaptedTransport, - new Pipe(AdaptedInputPipeOptions), - new Pipe(AdaptedOutputPipeOptions), - Log, - MemoryPool.GetMinimumAllocSize()); - - _adaptedTransport = adaptedPipeline; - } - // This feature should never be null in Kestrel var connectionHeartbeatFeature = _context.ConnectionFeatures.Get(); @@ -116,13 +67,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http _context.ConnectionFeatures.Set(_timeoutControl); - if (adaptedPipeline != null) - { - // Stream can be null here and run async will close the connection in that case - var stream = await ApplyConnectionAdaptersAsync(adaptedPipeline.TransportStream); - adaptedPipeline.RunAsync(stream); - } - IRequestProcessor requestProcessor = null; lock (_protocolSelectionLock) @@ -130,7 +74,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http // Ensure that the connection hasn't already been stopped. if (_protocolSelectionState == ProtocolSelectionState.Initializing) { - var derivedContext = CreateDerivedContext(_adaptedTransport); + var derivedContext = CreateDerivedContext(_context.Transport); switch (SelectProtocol()) { @@ -169,9 +113,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http await requestProcessor.ProcessRequestsAsync(httpApplication); } } - - // Complete the pipeline after the method runs - await (adaptedPipeline?.CompleteAsync() ?? Task.CompletedTask); } } catch (Exception ex) @@ -180,8 +121,6 @@ public async Task ProcessRequestsAsync(IHttpApplication http } finally { - DisposeAdaptedConnections(); - if (_http1Connection?.IsUpgraded == true) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); @@ -215,100 +154,89 @@ private HttpConnectionContext CreateDerivedContext(IDuplexPipe transport) private void StopProcessingNextRequest() { + ProtocolSelectionState previousState; lock (_protocolSelectionLock) { + previousState = _protocolSelectionState; + switch (_protocolSelectionState) { case ProtocolSelectionState.Initializing: - CloseUninitializedConnection(new ConnectionAbortedException(CoreStrings.ServerShutdownDuringConnectionInitialization)); _protocolSelectionState = ProtocolSelectionState.Aborted; break; case ProtocolSelectionState.Selected: - _requestProcessor.StopProcessingNextRequest(); - break; case ProtocolSelectionState.Aborted: break; } } + + switch (previousState) + { + case ProtocolSelectionState.Initializing: + _context.ConnectionContext.Abort(new ConnectionAbortedException(CoreStrings.ServerShutdownDuringConnectionInitialization)); + break; + case ProtocolSelectionState.Selected: + _requestProcessor.StopProcessingNextRequest(); + break; + case ProtocolSelectionState.Aborted: + break; + } } private void OnInputOrOutputCompleted() { + ProtocolSelectionState previousState; lock (_protocolSelectionLock) { + previousState = _protocolSelectionState; + switch (_protocolSelectionState) { case ProtocolSelectionState.Initializing: - // OnReader/WriterCompleted callbacks are not wired until after leaving the Initializing state. - Debug.Assert(false); - - CloseUninitializedConnection(new ConnectionAbortedException("HttpConnection.OnInputOrOutputCompleted() called while in the ProtocolSelectionState.Initializing state!?")); _protocolSelectionState = ProtocolSelectionState.Aborted; break; case ProtocolSelectionState.Selected: - _requestProcessor.OnInputOrOutputCompleted(); - break; case ProtocolSelectionState.Aborted: break; } - } - } - private void Abort(ConnectionAbortedException ex) - { - lock (_protocolSelectionLock) + switch (previousState) { - switch (_protocolSelectionState) - { - case ProtocolSelectionState.Initializing: - CloseUninitializedConnection(ex); - break; - case ProtocolSelectionState.Selected: - _requestProcessor.Abort(ex); - break; - case ProtocolSelectionState.Aborted: - break; - } + case ProtocolSelectionState.Initializing: + // ConnectionClosed callback is not wired up until after leaving the Initializing state. + Debug.Assert(false); - _protocolSelectionState = ProtocolSelectionState.Aborted; + _context.ConnectionContext.Abort(new ConnectionAbortedException("HttpConnection.OnInputOrOutputCompleted() called while in the ProtocolSelectionState.Initializing state!?")); + break; + case ProtocolSelectionState.Selected: + _requestProcessor.OnInputOrOutputCompleted(); + break; + case ProtocolSelectionState.Aborted: + break; } } - private async Task ApplyConnectionAdaptersAsync(RawStream stream) + private void Abort(ConnectionAbortedException ex) { - var connectionAdapters = _context.ConnectionAdapters; - var adapterContext = new ConnectionAdapterContext(_context.ConnectionContext, stream); - _adaptedConnections = new List(connectionAdapters.Count); + ProtocolSelectionState previousState; - try - { - for (var i = 0; i < connectionAdapters.Count; i++) - { - var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext); - _adaptedConnections.Add(adaptedConnection); - adapterContext = new ConnectionAdapterContext(_context.ConnectionContext, adaptedConnection.ConnectionStream); - } - } - catch (Exception ex) + lock (_protocolSelectionLock) { - Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}."); - - return null; + previousState = _protocolSelectionState; + _protocolSelectionState = ProtocolSelectionState.Aborted; } - return adapterContext.ConnectionStream; - } - - private void DisposeAdaptedConnections() - { - var adaptedConnections = _adaptedConnections; - if (adaptedConnections != null) + switch (previousState) { - for (var i = adaptedConnections.Count - 1; i >= 0; i--) - { - adaptedConnections[i].Dispose(); - } + case ProtocolSelectionState.Initializing: + _context.ConnectionContext.Abort(ex); + break; + case ProtocolSelectionState.Selected: + _requestProcessor.Abort(ex); + break; + case ProtocolSelectionState.Aborted: + break; } } @@ -362,11 +290,6 @@ private void Tick() _requestProcessor?.Tick(now); } - private void CloseUninitializedConnection(ConnectionAbortedException abortReason) - { - _context.ConnectionContext.Abort(abortReason); - } - public void OnTimeout(TimeoutReason reason) { // In the cases that don't log directly here, we expect the setter of the timeout to also be the input diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs index a2ac0839b0da..e46a2c2a831e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionBuilderExtensions.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { @@ -13,12 +12,7 @@ internal static class HttpConnectionBuilderExtensions { public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { - return builder.UseHttpServer(Array.Empty(), serviceContext, application, protocols); - } - - public static IConnectionBuilder UseHttpServer(this IConnectionBuilder builder, IList adapters, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) - { - var middleware = new HttpConnectionMiddleware(adapters, serviceContext, application, protocols); + var middleware = new HttpConnectionMiddleware(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs index 3198f6006b20..562b7bd1a965 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionContext.cs @@ -7,7 +7,6 @@ using System.Net; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal @@ -19,7 +18,6 @@ internal class HttpConnectionContext public ConnectionContext ConnectionContext { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } - public IList ConnectionAdapters { get; set; } public MemoryPool MemoryPool { get; set; } public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint RemoteEndPoint { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs index efae39ce71bb..c1c0aebc4728 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnectionMiddleware.cs @@ -1,38 +1,29 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal class HttpConnectionMiddleware { - private readonly IList _connectionAdapters; private readonly ServiceContext _serviceContext; private readonly IHttpApplication _application; private readonly HttpProtocols _protocols; - public HttpConnectionMiddleware(IList adapters, ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) + public HttpConnectionMiddleware(ServiceContext serviceContext, IHttpApplication application, HttpProtocols protocols) { _serviceContext = serviceContext; _application = application; _protocols = protocols; - - // Keeping these around for now so progress can be made without updating tests - _connectionAdapters = adapters; } public Task OnConnectionAsync(ConnectionContext connectionContext) { - // We need the transport feature so that we can cancel the output reader that the transport is using - // This is a bit of a hack but it preserves the existing semantics var memoryPoolFeature = connectionContext.Features.Get(); var httpConnectionContext = new HttpConnectionContext @@ -43,7 +34,6 @@ public Task OnConnectionAsync(ConnectionContext connectionContext) ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = memoryPoolFeature.MemoryPool, - ConnectionAdapters = _connectionAdapters, Transport = connectionContext.Transport }; diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs similarity index 54% rename from src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs rename to src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs index 46ad0ae1322e..2449fc514629 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionMiddleware.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Authentication; @@ -14,29 +15,27 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal { - internal class HttpsConnectionAdapter : IConnectionAdapter + internal class HttpsConnectionMiddleware { - private static readonly ClosedAdaptedConnection _closedAdaptedConnection = new ClosedAdaptedConnection(); - + private readonly ConnectionDelegate _next; private readonly HttpsConnectionAdapterOptions _options; + private readonly ILogger _logger; private readonly X509Certificate2 _serverCertificate; private readonly Func _serverCertificateSelector; - private readonly ILogger _logger; - - public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options) - : this(options, loggerFactory: null) + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options) + : this(next, options, loggerFactory: NullLoggerFactory.Instance) { } - public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory) + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory) { if (options == null) { @@ -49,6 +48,7 @@ public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options, ILoggerFact throw new NotSupportedException(CoreStrings.HTTP2NoTlsOsx); } + _next = next; // capture the certificate now so it can't be switched after validation _serverCertificate = options.ServerCertificate; _serverCertificateSelector = options.ServerCertificateSelector; @@ -69,33 +69,46 @@ public HttpsConnectionAdapter(HttpsConnectionAdapterOptions options, ILoggerFact } _options = options; - _logger = loggerFactory?.CreateLogger() ?? (ILogger)NullLogger.Instance; + _logger = loggerFactory?.CreateLogger(); } - - public bool IsHttps => true; - - public Task OnConnectionAsync(ConnectionAdapterContext context) + public Task OnConnectionAsync(ConnectionContext context) { - // Don't trust SslStream not to block. return Task.Run(() => InnerOnConnectionAsync(context)); } - private async Task InnerOnConnectionAsync(ConnectionAdapterContext context) + private async Task InnerOnConnectionAsync(ConnectionContext context) { - SslStream sslStream; bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set(feature); context.Features.Set(feature); + var memoryPool = context.Features.Get()?.MemoryPool; + + var inputPipeOptions = new StreamPipeReaderOptions + ( + pool: memoryPool, + bufferSize: memoryPool.GetMinimumSegmentSize(), + minimumReadSize: memoryPool.GetMinimumAllocSize(), + leaveOpen: true + ); + + var outputPipeOptions = new StreamPipeWriterOptions + ( + pool: memoryPool, + leaveOpen: true + ); + + SslDuplexPipe sslDuplexPipe = null; + if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { - sslStream = new SslStream(context.ConnectionStream); + sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { - sslStream = new SslStream(context.ConnectionStream, + sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream(s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { @@ -127,74 +140,73 @@ private async Task InnerOnConnectionAsync(ConnectionAdapterC } return true; - }); + })); certificateRequired = true; } - var timeoutFeature = context.Features.Get(); - timeoutFeature.SetTimeout(_options.HandshakeTimeout); + var sslStream = sslDuplexPipe.Stream; - try + using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) + using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { - // Adapt to the SslStream signature - ServerCertificateSelectionCallback selector = null; - if (_serverCertificateSelector != null) + try { - selector = (sender, name) => + // Adapt to the SslStream signature + ServerCertificateSelectionCallback selector = null; + if (_serverCertificateSelector != null) { - context.Features.Set(sslStream); - var cert = _serverCertificateSelector(context.ConnectionContext, name); - if (cert != null) + selector = (sender, name) => { - EnsureCertificateIsAllowedForServerAuth(cert); - } - return cert; + context.Features.Set(sslStream); + var cert = _serverCertificateSelector(context, name); + if (cert != null) + { + EnsureCertificateIsAllowedForServerAuth(cert); + } + return cert; + }; + } + + var sslOptions = new SslServerAuthenticationOptions + { + ServerCertificate = _serverCertificate, + ServerCertificateSelectionCallback = selector, + ClientCertificateRequired = certificateRequired, + EnabledSslProtocols = _options.SslProtocols, + CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, + ApplicationProtocols = new List() }; - } - var sslOptions = new SslServerAuthenticationOptions - { - ServerCertificate = _serverCertificate, - ServerCertificateSelectionCallback = selector, - ClientCertificateRequired = certificateRequired, - EnabledSslProtocols = _options.SslProtocols, - CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, - ApplicationProtocols = new List() - }; - - // This is order sensitive - if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) + // This is order sensitive + if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) + { + sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); + // https://tools.ietf.org/html/rfc7540#section-9.2.1 + sslOptions.AllowRenegotiation = false; + } + + if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) + { + sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); + } + + _options.OnAuthenticate?.Invoke(context, sslOptions); + + await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); + } + catch (OperationCanceledException) { - sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); - // https://tools.ietf.org/html/rfc7540#section-9.2.1 - sslOptions.AllowRenegotiation = false; + _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); + await sslStream.DisposeAsync(); + return; } - - if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) + catch (Exception ex) when (ex is IOException || ex is AuthenticationException) { - sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); + _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); + await sslStream.DisposeAsync(); + return; } - - _options.OnAuthenticate?.Invoke(context.ConnectionContext, sslOptions); - - await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); - } - catch (OperationCanceledException) - { - _logger.LogDebug(2, CoreStrings.AuthenticationTimedOut); - sslStream.Dispose(); - return _closedAdaptedConnection; - } - catch (Exception ex) when (ex is IOException || ex is AuthenticationException) - { - _logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed); - sslStream.Dispose(); - return _closedAdaptedConnection; - } - finally - { - timeoutFeature.CancelTimeout(); } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; @@ -208,7 +220,23 @@ private async Task InnerOnConnectionAsync(ConnectionAdapterC feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; - return new HttpsAdaptedConnection(sslStream); + var originalTransport = context.Transport; + + try + { + context.Transport = sslDuplexPipe; + + // Disposing the stream will dispose the sslDuplexPipe + await using (sslStream) + { + await _next(context); + } + } + finally + { + // Restore the original so that it gets closed appropriately + context.Transport = originalTransport; + } } private static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate) @@ -234,28 +262,16 @@ private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certif return new X509Certificate2(certificate); } - private class HttpsAdaptedConnection : IAdaptedConnection + private class SslDuplexPipe : DuplexPipeStreamAdapter { - private readonly SslStream _sslStream; - - public HttpsAdaptedConnection(SslStream sslStream) + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions) + : this(transport, readerOptions, writerOptions, s => new SslStream(s)) { - _sslStream = sslStream; - } - - public Stream ConnectionStream => _sslStream; - public void Dispose() - { - _sslStream.Dispose(); } - } - - private class ClosedAdaptedConnection : IAdaptedConnection - { - public Stream ConnectionStream { get; } = new ClosedStream(); - public void Dispose() + public SslDuplexPipe(IDuplexPipe transport, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func factory) : + base(transport, readerOptions, writerOptions, factory) { } } diff --git a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs index df024e8750f6..4522fedd6293 100644 --- a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs @@ -256,7 +256,7 @@ public void Load() } // EndpointDefaults or configureEndpoint may have added an https adapter. - if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps)) + if (https && !listenOptions.IsTls) { if (httpsOptions.ServerCertificate == null && httpsOptions.ServerCertificateSelector == null) { diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs index 0481df8fad71..6eb5ada99de4 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServer.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO.Pipelines; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -123,7 +124,7 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options) { // Add the HTTP middleware as the terminal connection middleware - options.UseHttpServer(options.ConnectionAdapters, ServiceContext, application, options.Protocols); + options.UseHttpServer(ServiceContext, application, options.Protocols); var connectionDelegate = options.Build(); diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index 85651e1956ba..14e483c403dd 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core @@ -63,7 +61,7 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) public ulong FileHandle => (EndPoint as FileHandleEndPoint)?.FileHandle ?? 0; /// - /// Enables an to resolve and use services registered by the application during startup. + /// Enables connection middleware to resolve and use services registered by the application during startup. /// Only set if accessed from the callback of a Listen* method. /// public KestrelServerOptions KestrelServerOptions { get; internal set; } @@ -74,38 +72,37 @@ internal ListenOptions(ulong fileHandle, FileHandleType handleType) /// Defaults to HTTP/1.x and HTTP/2. public HttpProtocols Protocols { get; set; } = HttpProtocols.Http1AndHttp2; - /// - /// Gets the that allows each connection - /// to be intercepted and transformed. - /// Configured by the UseHttps() and - /// extension methods. - /// - /// - /// Defaults to empty. - /// -#pragma warning disable PUB0001 // Pubternal type in public API - public List ConnectionAdapters { get; } = new List(); -#pragma warning restore PUB0001 // Pubternal type in public API - public IServiceProvider ApplicationServices => KestrelServerOptions?.ApplicationServices; + internal string Scheme + { + get + { + if (IsHttp) + { + return IsTls ? "https" : "http"; + } + return "tcp"; + } + } + + internal bool IsHttp { get; set; } = true; + + internal bool IsTls { get; set; } + /// /// Gets the name of this endpoint to display on command-line when the web server starts. /// internal virtual string GetDisplayName() { - var scheme = ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - switch (EndPoint) { case IPEndPoint _: - return $"{scheme}://{IPEndPoint}"; + return $"{Scheme}://{IPEndPoint}"; case UnixDomainSocketEndPoint _: - return $"{scheme}://unix:{EndPoint}"; + return $"{Scheme}://unix:{EndPoint}"; case FileHandleEndPoint _: - return $"{scheme}://"; + return $"{Scheme}://"; default: throw new InvalidOperationException(); } @@ -113,6 +110,13 @@ internal virtual string GetDisplayName() public override string ToString() => GetDisplayName(); + /// + /// Adds a middleware delegate to the connection pipeline. + /// Configured by the UseHttps() and + /// extension methods. + /// + /// The middleware delegate. + /// The . public IConnectionBuilder Use(Func middleware) { _middleware.Add(middleware); diff --git a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs index e1950bd52735..e3d411e08772 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Hosting { @@ -25,7 +26,7 @@ public static class ListenOptionsHttpsExtensions /// The to configure. /// The . public static ListenOptions UseHttps(this ListenOptions listenOptions) => listenOptions.UseHttps(_ => { }); - + /// /// Configure Kestrel to use HTTPS. /// @@ -185,6 +186,7 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, ActionThe . public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions) { - var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService(); + var loggerFactory = listenOptions.KestrelServerOptions?.ApplicationServices.GetRequiredService() ?? NullLoggerFactory.Instance; + // Set the list of protocols from listen options httpsOptions.HttpProtocols = listenOptions.Protocols; - listenOptions.ConnectionAdapters.Add(new HttpsConnectionAdapter(httpsOptions, loggerFactory)); + listenOptions.IsTls = true; + + listenOptions.Use(next => + { + var middleware = new HttpsConnectionMiddleware(next, httpsOptions, loggerFactory); + return middleware.OnConnectionAsync; + }); return listenOptions; } } diff --git a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs index c7e5f47cadb5..1c465cb14720 100644 --- a/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; @@ -28,11 +27,7 @@ internal LocalhostListenOptions(int port) /// internal override string GetDisplayName() { - var scheme = ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - - return $"{scheme}://localhost:{IPEndPoint.Port}"; + return $"{Scheme}://localhost:{IPEndPoint.Port}"; } internal override async Task BindAsync(AddressBindContext context) @@ -78,10 +73,10 @@ internal ListenOptions Clone(IPAddress address) { KestrelServerOptions = KestrelServerOptions, Protocols = Protocols, + IsTls = IsTls }; options._middleware.AddRange(_middleware); - options.ConnectionAdapters.AddRange(ConnectionAdapters); return options; } } diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs similarity index 97% rename from src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs rename to src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs index 726dcaa3292b..1cdde1631340 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/RawStream.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStream.cs @@ -8,16 +8,16 @@ using System.Threading.Tasks; using System.Buffers; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { - internal sealed class RawStream : Stream + internal class DuplexPipeStream : Stream { private readonly PipeReader _input; private readonly PipeWriter _output; private readonly bool _throwOnCancelled; private volatile bool _cancelCalled; - public RawStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false) + public DuplexPipeStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false) { _input = input; _output = output; diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs new file mode 100644 index 000000000000..5b5cc019057c --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/DuplexPipeStreamAdapter.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.IO.Pipelines; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + /// + /// A helper for wrapping a Stream decorator from an . + /// + /// + internal class DuplexPipeStreamAdapter : DuplexPipeStream, IDuplexPipe where TStream : Stream + { + public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func createStream) : + this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream) + { + } + + public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func createStream) : base(duplexPipe.Input, duplexPipe.Output) + { + Stream = createStream(this); + Input = PipeReader.Create(Stream, readerOptions); + Output = PipeWriter.Create(Stream, writerOptions); + } + + public TStream Stream { get; } + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + protected override void Dispose(bool disposing) + { + Input.Complete(); + Output.Complete(); + base.Dispose(disposing); + } + + public override ValueTask DisposeAsync() + { + Input.Complete(); + Output.Complete(); + return base.DisposeAsync(); + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs new file mode 100644 index 000000000000..ed9ffb819c7b --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingConnectionMiddleware.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO.Pipelines; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class LoggingConnectionMiddleware + { + private readonly ConnectionDelegate _next; + private readonly ILogger _logger; + + public LoggingConnectionMiddleware(ConnectionDelegate next, ILogger logger) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task OnConnectionAsync(ConnectionContext context) + { + var oldTransport = context.Transport; + + try + { + await using (var loggingDuplexPipe = new LoggingDuplexPipe(context.Transport, _logger)) + { + context.Transport = loggingDuplexPipe; + + await _next(context); + } + } + finally + { + context.Transport = oldTransport; + } + } + + private class LoggingDuplexPipe : DuplexPipeStreamAdapter + { + public LoggingDuplexPipe(IDuplexPipe transport, ILogger logger) : + base(transport, stream => new LoggingStream(stream, logger)) + { + } + } + } +} diff --git a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs similarity index 99% rename from src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs rename to src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs index 71ed417a1177..e3fdec3f8180 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/Internal/LoggingStream.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/Internal/LoggingStream.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { internal sealed class LoggingStream : Stream { diff --git a/src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs b/src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs similarity index 85% rename from src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs rename to src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs index ef95004a3074..de4f8a62a554 100644 --- a/src/Servers/Kestrel/Core/src/Adapter/ListenOptionsConnectionLoggingExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/ListenOptionsConnectionLoggingExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -30,8 +30,8 @@ public static ListenOptions UseConnectionLogging(this ListenOptions listenOption public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName) { var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService(); - var logger = loggerName == null ? loggerFactory.CreateLogger() : loggerFactory.CreateLogger(loggerName); - listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger)); + var logger = loggerName == null ? loggerFactory.CreateLogger() : loggerFactory.CreateLogger(loggerName); + listenOptions.Use(next => new LoggingConnectionMiddleware(next, logger).OnConnectionAsync); return listenOptions; } } diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 8af4397809be..36add68a21a1 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -61,7 +61,7 @@ public void StartWithHttpsAddressConfiguresHttpsEndpoints() StartDummyApplication(server); Assert.True(server.Options.ListenOptions.Any()); - Assert.Contains(server.Options.ListenOptions[0].ConnectionAdapters, adapter => adapter.IsHttps); + Assert.True(server.Options.ListenOptions[0].IsTls); } } diff --git a/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs b/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs index 998d0a34a43a..3eb462679639 100644 --- a/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs +++ b/src/Servers/Kestrel/Core/test/ListenOptionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; @@ -22,7 +22,6 @@ public void ProtocolsDefault() public void LocalHostListenOptionsClonesConnectionMiddleware() { var localhostListenOptions = new LocalhostListenOptions(1004); - localhostListenOptions.ConnectionAdapters.Add(new PassThroughConnectionAdapter()); var serviceProvider = new ServiceCollection().BuildServiceProvider(); localhostListenOptions.KestrelServerOptions = new KestrelServerOptions { @@ -45,7 +44,6 @@ public void LocalHostListenOptionsClonesConnectionMiddleware() Assert.NotNull(clone.KestrelServerOptions); Assert.NotNull(serviceProvider); Assert.Same(serviceProvider, clone.ApplicationServices); - Assert.Single(clone.ConnectionAdapters); } } } diff --git a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs b/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs deleted file mode 100644 index 0b06bec0e9eb..000000000000 --- a/src/Servers/Kestrel/Core/test/PipeOptionsTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO.Pipelines; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Testing; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests -{ - public class PipeOptionsTests - { - [Theory] - [InlineData(10, 10, 10)] - [InlineData(null, 0, 0)] - public void AdaptedInputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; - - var connectionLifetime = new HttpConnection(new HttpConnectionContext - { - ServiceContext = serviceContext - }); - - Assert.Equal(expectedMaximumSizeLow, connectionLifetime.AdaptedInputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, connectionLifetime.AdaptedInputPipeOptions.PauseWriterThreshold); - Assert.Same(serviceContext.Scheduler, connectionLifetime.AdaptedInputPipeOptions.ReaderScheduler); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedInputPipeOptions.WriterScheduler); - } - - [Theory] - [InlineData(10, 10, 10)] - [InlineData(null, 0, 0)] - public void AdaptedOutputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) - { - var serviceContext = new TestServiceContext(); - serviceContext.ServerOptions.Limits.MaxResponseBufferSize = maxRequestBufferSize; - - var connectionLifetime = new HttpConnection(new HttpConnectionContext - { - ServiceContext = serviceContext - }); - - Assert.Equal(expectedMaximumSizeLow, connectionLifetime.AdaptedOutputPipeOptions.ResumeWriterThreshold); - Assert.Equal(expectedMaximumSizeHigh, connectionLifetime.AdaptedOutputPipeOptions.PauseWriterThreshold); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedOutputPipeOptions.ReaderScheduler); - Assert.Same(PipeScheduler.Inline, connectionLifetime.AdaptedOutputPipeOptions.WriterScheduler); - } - } -} diff --git a/src/Servers/Kestrel/Kestrel/test/HttpsConnectionAdapterOptionsTest.cs b/src/Servers/Kestrel/Kestrel/test/HttpsConnectionAdapterOptionsTest.cs index 13b80b629bab..aa4c0036610a 100644 --- a/src/Servers/Kestrel/Kestrel/test/HttpsConnectionAdapterOptionsTest.cs +++ b/src/Servers/Kestrel/Kestrel/test/HttpsConnectionAdapterOptionsTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs index f3bedad135c4..fbcf3b568ea6 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs @@ -168,8 +168,8 @@ public void ConfigureDefaultsAppliesToNewConfigureEndpoints() Assert.True(ran1); Assert.True(ran2); - Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); - Assert.Null(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); + Assert.True(serverOptions.ListenOptions[0].IsTls); + Assert.False(serverOptions.ListenOptions[1].IsTls); } [Fact] @@ -210,8 +210,8 @@ public void ConfigureEndpointDefaultCanEnableHttps() Assert.True(ran2); // You only get Https once per endpoint. - Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); - Assert.NotNull(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); + Assert.True(serverOptions.ListenOptions[0].IsTls); + Assert.True(serverOptions.ListenOptions[1].IsTls); } [Fact] diff --git a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs index d18af3aa39bf..f2b415966728 100644 --- a/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs +++ b/src/Servers/Kestrel/Transport.Libuv/test/LibuvTransportTests.cs @@ -185,7 +185,7 @@ public async Task OneToTenThreads(int threadCount) return context.Response.WriteAsync("Hello World"); }); - listenOptions.UseHttpServer(listenOptions.ConnectionAdapters, serviceContext, testApplication, HttpProtocols.Http1); + listenOptions.UseHttpServer(serviceContext, testApplication, HttpProtocols.Http1); var transportContext = new TestLibuvTransportContext { diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs index 794f011ecc7b..c6b37eb216b0 100644 --- a/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http2SampleApp/Program.cs @@ -2,11 +2,10 @@ using System.IO; using System.Net; using System.Security.Authentication; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -39,7 +38,20 @@ public static void Main(string[] args) { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; listenOptions.UseHttps(); - listenOptions.ConnectionAdapters.Add(new TlsFilterAdapter()); + listenOptions.Use((context, next) => + { + // https://tools.ietf.org/html/rfc7540#appendix-A + // Allows filtering TLS handshakes on a per connection basis + + var tlsFeature = context.Features.Get(); + + if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null) + { + throw new NotSupportedException("Prohibited cipher: " + tlsFeature.CipherAlgorithm); + } + + return next(); + }); }); // Prior knowledge, no TLS handshake. WARNING: Not supported by browsers @@ -54,38 +66,5 @@ public static void Main(string[] args) hostBuilder.Build().Run(); } - - // https://tools.ietf.org/html/rfc7540#appendix-A - // Allows filtering TLS handshakes on a per connection basis - private class TlsFilterAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - var tlsFeature = context.Features.Get(); - - if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null) - { - throw new NotSupportedException("Prohibited cipher: " + tlsFeature.CipherAlgorithm); - } - - return Task.FromResult(new AdaptedConnection(context.ConnectionStream)); - } - - private class AdaptedConnection : IAdaptedConnection - { - public AdaptedConnection(Stream adaptedStream) - { - ConnectionStream = adaptedStream; - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } - } - } } } diff --git a/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs b/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs index 9f81ceec5fd0..a5951b6d6c72 100644 --- a/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs +++ b/src/Servers/Kestrel/shared/test/PassThroughConnectionAdapter.cs @@ -2,168 +2,96 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; +using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Connections; namespace Microsoft.AspNetCore.Testing { - public class PassThroughConnectionAdapter : IConnectionAdapter + public class PassThroughConnectionMiddleware { - public bool IsHttps => false; + private readonly ConnectionDelegate _next; - public Task OnConnectionAsync(ConnectionAdapterContext context) + public PassThroughConnectionMiddleware(ConnectionDelegate next) { - var adapted = new AdaptedConnection(new PassThroughStream(context.ConnectionStream)); - return Task.FromResult(adapted); + _next = next; } - private class AdaptedConnection : IAdaptedConnection + public Task OnConnectionAsync(ConnectionContext context) { - public AdaptedConnection(Stream stream) - { - ConnectionStream = stream; - } - - public Stream ConnectionStream { get; } - - public void Dispose() - { - } + context.Transport = new PassThroughDuplexPipe(context.Transport); + return _next(context); } - private class PassThroughStream : Stream + private class PassThroughDuplexPipe : IDuplexPipe { - private readonly Stream _innerStream; - - public PassThroughStream(Stream innerStream) + public PassThroughDuplexPipe(IDuplexPipe duplexPipe) { - _innerStream = innerStream; + Input = new PassThroughPipeReader(duplexPipe.Input); + Output = new PassThroughPipeWriter(duplexPipe.Output); } - public override bool CanRead => _innerStream.CanRead; - - public override bool CanSeek => _innerStream.CanSeek; + public PipeReader Input { get; } - public override bool CanTimeout => _innerStream.CanTimeout; + public PipeWriter Output { get; } - public override bool CanWrite => _innerStream.CanWrite; - - public override long Length => _innerStream.Length; - - public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } - - public override int ReadTimeout { get => _innerStream.ReadTimeout; set => _innerStream.ReadTimeout = value; } - - public override int WriteTimeout { get => _innerStream.WriteTimeout; set => _innerStream.WriteTimeout = value; } - - public override int Read(byte[] buffer, int offset, int count) + private class PassThroughPipeWriter : PipeWriter { - return _innerStream.Read(buffer, offset, count); - } + private PipeWriter _output; - public override int ReadByte() - { - return _innerStream.ReadByte(); - } + public PassThroughPipeWriter(PipeWriter output) + { + _output = output; + } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _innerStream.ReadAsync(buffer, offset, count, cancellationToken); - } + public override void Advance(int bytes) => _output.Advance(bytes); - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _innerStream.BeginRead(buffer, offset, count, callback, state); - } + public override void CancelPendingFlush() => _output.CancelPendingFlush(); - public override int EndRead(IAsyncResult asyncResult) - { - return _innerStream.EndRead(asyncResult); - } + public override void Complete(Exception exception = null) => _output.Complete(exception); - public override void Write(byte[] buffer, int offset, int count) - { - _innerStream.Write(buffer, offset, count); - } + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) => _output.FlushAsync(cancellationToken); + public override Memory GetMemory(int sizeHint = 0) => _output.GetMemory(sizeHint); - public override void WriteByte(byte value) - { - _innerStream.WriteByte(value); - } + public override Span GetSpan(int sizeHint = 0) => _output.GetSpan(sizeHint); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _innerStream.WriteAsync(buffer, offset, count, cancellationToken); + public override void OnReaderCompleted(Action callback, object state) => _output.OnReaderCompleted(callback, state); } - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + private class PassThroughPipeReader : PipeReader { - return _innerStream.BeginWrite(buffer, offset, count, callback, state); - } + private PipeReader _input; - public override void EndWrite(IAsyncResult asyncResult) - { - _innerStream.EndWrite(asyncResult); - } + public PassThroughPipeReader(PipeReader input) + { + _input = input; + } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken); - } + public override void AdvanceTo(SequencePosition consumed) => _input.AdvanceTo(consumed); - public override void Flush() - { - _innerStream.Flush(); - } + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) => _input.AdvanceTo(consumed, examined); - public override Task FlushAsync(CancellationToken cancellationToken) - { - return _innerStream.FlushAsync(); + public override void CancelPendingRead() => _input.CancelPendingRead(); - } + public override void Complete(Exception exception = null) => _input.Complete(exception); - public override long Seek(long offset, SeekOrigin origin) - { - return _innerStream.Seek(offset, origin); - } + public override void OnWriterCompleted(Action callback, object state) => _input.OnWriterCompleted(callback, state); - public override void SetLength(long value) - { - _innerStream.SetLength(value); - } + public override ValueTask ReadAsync(CancellationToken cancellationToken = default) => _input.ReadAsync(cancellationToken); - public override void Close() - { - _innerStream.Close(); - } - - public override int Read(Span buffer) - { - return _innerStream.Read(buffer); - } - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - return _innerStream.ReadAsync(buffer, cancellationToken); - } - - public override void Write(ReadOnlySpan buffer) - { - _innerStream.Write(buffer); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - return _innerStream.WriteAsync(buffer, cancellationToken); + public override bool TryRead(out ReadResult result) => _input.TryRead(out result); } + } + } - public override void CopyTo(Stream destination, int bufferSize) - { - _innerStream.CopyTo(destination, bufferSize); - } + public static class PassThroughConnectionMiddlewareExtensions + { + public static TBuilder UsePassThrough(this TBuilder builder) where TBuilder : IConnectionBuilder + { + builder.Use(next => new PassThroughConnectionMiddleware(next).OnConnectionAsync); + return builder; } } } diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs index 1382a7017599..3db27b32ea7f 100644 --- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs +++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs @@ -42,12 +42,20 @@ public TestServer(RequestDelegate app, TestServiceContext context) } public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions) - : this(app, context, listenOptions, _ => { }) + : this(app, context, options => options.ListenOptions.Add(listenOptions), _ => { }) { } - public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, Action configureServices) - : this(app, context, options => options.ListenOptions.Add(listenOptions), configureServices) + public TestServer(RequestDelegate app, TestServiceContext context, Action configureListenOptions) + : this(app, context, options => + { + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + { + KestrelServerOptions = options + }; + configureListenOptions(listenOptions); + options.ListenOptions.Add(listenOptions); + }, _ => { }) { } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs index 9e0f462a831d..eb2e9d493013 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ConnectionAdapterTests.cs @@ -6,7 +6,6 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; using Xunit; @@ -18,12 +17,10 @@ public class ConnectionAdapterTests : LoggedTest [Fact] public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => throw new Exception()); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions)) { @@ -47,15 +44,5 @@ await connection.Send( await server.StopAsync(); } } - - private class ThrowingConnectionAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) - { - throw new Exception(); - } - } } } diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs index eccfa17c2e0e..7d7717ba1337 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs @@ -44,7 +44,7 @@ public void TlsAndHttp2NotSupportedOnMac() var ex = Assert.Throws(() => new TestServer(context => { throw new NotImplementedException(); - }, new TestServiceContext(LoggerFactory), + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, kestrelOptions => { kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions => @@ -71,7 +71,7 @@ public async Task TlsAlpnHandshakeSelectsHttp2From1and2() "ALPN: " + tlsFeature.ApplicationProtocol.Length); return context.Response.WriteAsync("hello world " + context.Request.Protocol); - }, new TestServiceContext(LoggerFactory), + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, kestrelOptions => { kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions => @@ -102,7 +102,7 @@ public async Task TlsAlpnHandshakeSelectsHttp2() "ALPN: " + tlsFeature.ApplicationProtocol.Length); return context.Response.WriteAsync("hello world " + context.Request.Protocol); - }, new TestServiceContext(LoggerFactory), + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, kestrelOptions => { kestrelOptions.Listen(IPAddress.Loopback, 0, listenOptions => diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs index 6020455633b1..28e662be3a96 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs @@ -60,7 +60,7 @@ public async Task GracefulShutdownWaitsForRequestsToFinish() .Setup(m => m.Http2ConnectionClosing(It.IsAny())) .Callback(() => requestStopping.SetResult(null)); - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object); + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ExpectedConnectionMiddlewareCount = 1 }; testContext.InitializeHeartbeat(); @@ -102,6 +102,7 @@ public async Task GracefulShutdownWaitsForRequestsToFinish() } [ConditionalFact] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2667", FlakyOn.All)] public async Task GracefulTurnsAbortiveIfRequestsDoNotFinish() { var requestStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -111,7 +112,8 @@ public async Task GracefulTurnsAbortiveIfRequestsDoNotFinish() var testContext = new TestServiceContext(LoggerFactory) { - MemoryPoolFactory = memoryPoolFactory.Create + MemoryPoolFactory = memoryPoolFactory.Create, + ExpectedConnectionMiddlewareCount = 1 }; TestApplicationErrorLogger.ThrowOnUngracefulShutdown = false; diff --git a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs index 039b148db018..95403f42816b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs @@ -297,7 +297,7 @@ private async Task StartWebHost(long? maxRequestBufferSize, { if (useConnectionAdapter) { - listenOptions.ConnectionAdapters.Add(new PassThroughConnectionAdapter()); + listenOptions.UsePassThrough(); } }); diff --git a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs index 8e1555aa2e75..cac39502328d 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/RequestTests.cs @@ -40,10 +40,7 @@ public class RequestTests : LoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Theory] @@ -509,7 +506,7 @@ public async Task AbortingTheConnectionSendsFIN() [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnClientFIN(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var appStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -546,7 +543,7 @@ await connection.Send( [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnServerFIN(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(context => @@ -583,7 +580,7 @@ await connection.ReceiveEnd($"HTTP/1.1 200 OK", [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedTokenFiresOnServerAbort(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(context => @@ -628,7 +625,7 @@ public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions) // This needs a timeout. const int applicationAbortedConnectionId = 34; - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var readTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var registrationTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -746,6 +743,7 @@ public async Task ServerCanAbortConnectionAfterUnobservedClose(ListenOptions lis var mockKestrelTrace = new Mock(); var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { + ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count, ServerOptions = { Limits = @@ -805,7 +803,7 @@ public async Task AppCanHandleClientAbortingConnectionMidRequest(ListenOptions l var appStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock(); - var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object); + var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var scratchBuffer = new byte[4096]; diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index bb1dc036d7a8..3958bcd97c4b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -36,10 +36,7 @@ public class ResponseTests : TestApplicationErrorLoggerLoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Fact] @@ -160,7 +157,7 @@ public async Task WriteAfterConnectionCloseNoops(ListenOptions listenOptions) { appCompleted.TrySetException(ex); } - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -222,7 +219,7 @@ public async Task ThrowsOnWriteWithRequestAbortedTokenAfterRequestIsAborted(List } writeTcs.SetException(new Exception("This shouldn't be reached.")); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -276,6 +273,7 @@ public async Task WritingToConnectionAfterUnobservedCloseTriggersRequestAbortedT var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object) { + ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count, ServerOptions = { Limits = @@ -370,7 +368,7 @@ public async Task AppCanHandleClientAbortingConnectionMidResponse(ListenOptions await requestAborted.Task.DefaultTimeout(); appCompletedTcs.SetResult(null); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }, listenOptions)) { using (var connection = server.CreateConnection()) { @@ -424,7 +422,8 @@ public async Task ClientAbortingConnectionImmediatelyIsNotLoggedHigherThanDebug( // There's not guarantee that the app even gets invoked in this test. The connection reset can be observed // as early as accept. - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + var testServiceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; + using (var server = new TestServer(context => Task.CompletedTask, testServiceContext, listenOptions)) { for (var i = 0; i < numConnections; i++) { @@ -583,18 +582,16 @@ public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate { MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2)) } - } + }, + ExpectedConnectionMiddlewareCount = 1 }; testContext.InitializeHeartbeat(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = certificate }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = certificate }); + } using (var server = new TestServer(async context => { @@ -621,7 +618,7 @@ public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate { await aborted.Task.DefaultTimeout(); } - }, testContext, listenOptions)) + }, testContext, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs index 8b1387f88ba4..b7f5b02de227 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ConnectionAdapterTests.cs @@ -7,10 +7,10 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; @@ -20,7 +20,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests { public class ConnectionAdapterTests : TestApplicationErrorLoggerLoggedTest { - public static TheoryData EchoAppRequestDelegates => new TheoryData { @@ -32,13 +31,16 @@ public class ConnectionAdapterTests : TestApplicationErrorLoggerLoggedTest [MemberData(nameof(EchoAppRequestDelegates))] public async Task CanReadAndWriteWithRewritingConnectionAdapter(RequestDelegate requestDelegate) { - var adapter = new RewritingConnectionAdapter(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + RewritingConnectionMiddleware middleware = null; + + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => { - ConnectionAdapters = { adapter } - }; + middleware = new RewritingConnectionMiddleware(next); + return middleware.OnConnectionAsync; + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; var sendString = "POST / HTTP/1.0\r\nContent-Length: 12\r\n\r\nHello World?"; @@ -57,19 +59,17 @@ await connection.ReceiveEnd( } } - Assert.Equal(sendString.Length, adapter.BytesRead); + Assert.Equal(sendString.Length, middleware.BytesRead); } [Theory] [MemberData(nameof(EchoAppRequestDelegates))] public async Task CanReadAndWriteWithAsyncConnectionAdapter(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -94,12 +94,10 @@ await connection.ReceiveEnd( [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateFinAfterOnConnectionAsyncClosesGracefully(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -116,12 +114,10 @@ public async Task ImmediateFinAfterOnConnectionAsyncClosesGracefully(RequestDele [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateFinAfterThrowingClosesGracefully(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => throw new InvalidOperationException()); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -139,12 +135,10 @@ public async Task ImmediateFinAfterThrowingClosesGracefully(RequestDelegate requ [MemberData(nameof(EchoAppRequestDelegates))] public async Task ImmediateShutdownAfterOnConnectionAsyncDoesNotCrash(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new AsyncConnectionAdapter() } - }; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => new AsyncConnectionMiddleware(next).OnConnectionAsync); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; TestApplicationErrorLogger.ThrowOnUngracefulShutdown = false; @@ -167,13 +161,18 @@ public async Task ImmediateShutdownAfterOnConnectionAsyncDoesNotCrash(RequestDel [Fact] public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() { - var waitingConnectionAdapter = new WaitingConnectionAdapter(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => { - ConnectionAdapters = { waitingConnectionAdapter } - }; + return async context => + { + await tcs.Task; + await next(context); + }; + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions)) { @@ -181,13 +180,9 @@ public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() using (var connection = server.CreateConnection()) { - var closingMessageTask = TestApplicationErrorLogger.WaitForMessage(m => m.Message.Contains(CoreStrings.ServerShutdownDuringConnectionInitialization)); - stopTask = server.StopAsync(); - await closingMessageTask.DefaultTimeout(); - - waitingConnectionAdapter.Complete(); + tcs.TrySetResult(null); } await stopTask; @@ -198,12 +193,15 @@ public async Task ImmediateShutdownDuringOnConnectionAsyncDoesNotCrash() [MemberData(nameof(EchoAppRequestDelegates))] public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer(RequestDelegate requestDelegate) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var connectionId = ""; + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.Use(next => context => { - ConnectionAdapters = { new ThrowingConnectionAdapter() } - }; + connectionId = context.ConnectionId; + throw new InvalidOperationException(); + }); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(requestDelegate, serviceContext, listenOptions)) { @@ -218,18 +216,16 @@ await connection.Send( } } - Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains($"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.")); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Unhandled exception while processing " + connectionId + ".")); } [Fact] public async Task CanFlushAsyncWithConnectionAdapter() { var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - }; + .UsePassThrough(); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(async context => { @@ -258,11 +254,9 @@ await connection.ReceiveEnd( public async Task CanFlushAsyncWithConnectionAdapterPipeWriter() { var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - }; + .UsePassThrough(); - var serviceContext = new TestServiceContext(LoggerFactory); + var serviceContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; await using (var server = new TestServer(async context => { @@ -287,71 +281,67 @@ await connection.ReceiveEnd( } } - private class RewritingConnectionAdapter : IConnectionAdapter + private class RewritingConnectionMiddleware { private RewritingStream _rewritingStream; + private readonly ConnectionDelegate _next; - public bool IsHttps => false; - - public Task OnConnectionAsync(ConnectionAdapterContext context) + public RewritingConnectionMiddleware(ConnectionDelegate next) { - _rewritingStream = new RewritingStream(context.ConnectionStream); - return Task.FromResult(new AdaptedConnection(_rewritingStream)); + _next = next; } - public int BytesRead => _rewritingStream.BytesRead; - } - - private class AsyncConnectionAdapter : IConnectionAdapter - { - public bool IsHttps => false; - - public async Task OnConnectionAsync(ConnectionAdapterContext context) + public async Task OnConnectionAsync(ConnectionContext context) { - await Task.Yield(); - return new AdaptedConnection(new RewritingStream(context.ConnectionStream)); - } - } - - private class WaitingConnectionAdapter : IConnectionAdapter - { - private TaskCompletionSource _waitingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var old = context.Transport; + var duplexPipe = new DuplexPipeStreamAdapter(context.Transport, s => new RewritingStream(s)); + _rewritingStream = duplexPipe.Stream; - public bool IsHttps => false; - - public async Task OnConnectionAsync(ConnectionAdapterContext context) - { - await _waitingTcs.Task; - return new AdaptedConnection(context.ConnectionStream); + try + { + await using (duplexPipe) + { + context.Transport = duplexPipe; + await _next(context); + } + } + finally + { + context.Transport = old; + } } - public void Complete() - { - _waitingTcs.TrySetResult(null); - } + public int BytesRead => _rewritingStream.BytesRead; } - private class ThrowingConnectionAdapter : IConnectionAdapter + private class AsyncConnectionMiddleware { - public bool IsHttps => false; + private readonly ConnectionDelegate _next; - public Task OnConnectionAsync(ConnectionAdapterContext context) + public AsyncConnectionMiddleware(ConnectionDelegate next) { - throw new Exception(); + _next = next; } - } - private class AdaptedConnection : IAdaptedConnection - { - public AdaptedConnection(Stream adaptedStream) + public async Task OnConnectionAsync(ConnectionContext context) { - ConnectionStream = adaptedStream; - } + await Task.Yield(); - public Stream ConnectionStream { get; } + var old = context.Transport; + var duplexPipe = new DuplexPipeStreamAdapter(context.Transport, s => new RewritingStream(s)); - public void Dispose() - { + try + { + await using (duplexPipe) + { + context.Transport = duplexPipe; + await _next(context); + } + } + finally + { + context.Transport = old; + } } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs index 75d04b3285ce..a1646a8fbdd5 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/PipeReaderFactory.cs @@ -6,7 +6,7 @@ using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 { @@ -34,7 +34,7 @@ private static async Task CopyToAsync(Stream stream, Pipe pipe, CancellationToke { try { - await stream.CopyToAsync(new RawStream(null, pipe.Writer), bufferSize: 4096, cancellationToken); + await stream.CopyToAsync(new DuplexPipeStream(null, pipe.Writer), bufferSize: 4096, cancellationToken); } catch (OperationCanceledException) { @@ -49,4 +49,4 @@ private static async Task CopyToAsync(Stream stream, Pipe pipe, CancellationToke } } } -} \ No newline at end of file +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index 283d0c2e2def..275fcb999767 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs @@ -30,7 +30,7 @@ public class TlsTests : LoggedTest private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); [ConditionalFact] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7000 ")] + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7000")] public async Task TlsHandshakeRejectsTlsLessThan12() { using (var server = new TestServer(context => @@ -41,7 +41,7 @@ public async Task TlsHandshakeRejectsTlsLessThan12() return context.Response.WriteAsync("hello world " + context.Request.Protocol); }, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1}, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs similarity index 69% rename from src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs rename to src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs index f2a4d4113634..cd06d0a3d1a3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs @@ -13,6 +13,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -34,15 +35,12 @@ public class HttpsConnectionAdapterTests : LoggedTest [Fact] public async Task CanReadAndWriteWithHttpsConnectionAdapter() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); }; - await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/", new FormUrlEncodedContent(new[] { @@ -57,12 +55,9 @@ public async Task CanReadAndWriteWithHttpsConnectionAdapter() [Fact] public async Task HandshakeDetailsAreAvailable() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); }; await using (var server = new TestServer(context => @@ -78,7 +73,7 @@ public async Task HandshakeDetailsAreAvailable() Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("hello world", result); @@ -88,20 +83,14 @@ public async Task HandshakeDetailsAreAvailable() [Fact] public async Task RequireCertificateFailsWhenNoCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate - }) - } - }; - + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.RequireCertificate + }); - await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions)) { await Assert.ThrowsAnyAsync( () => server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/")); @@ -111,17 +100,14 @@ await Assert.ThrowsAnyAsync( [Fact] public async Task AllowCertificateContinuesWhenNoCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.AllowCertificate - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.AllowCertificate + }); + } await using (var server = new TestServer(context => { @@ -129,7 +115,7 @@ public async Task AllowCertificateContinuesWhenNoCertificate() Assert.NotNull(tlsFeature); Assert.Null(tlsFeature.ClientCertificate); return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("hello world", result); @@ -139,7 +125,7 @@ public async Task AllowCertificateContinuesWhenNoCertificate() [Fact] public void ThrowsWhenNoServerCertificateIsProvided() { - Assert.Throws(() => new HttpsConnectionAdapter( + Assert.Throws(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions()) ); } @@ -147,15 +133,12 @@ public void ThrowsWhenNoServerCertificateIsProvided() [Fact] public async Task UsesProvidedServerCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -173,24 +156,22 @@ public async Task UsesProvidedServerCertificate() public async Task UsesProvidedServerCertificateSelector() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (connection, name) => { - ServerCertificateSelector = (connection, name) => - { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); - Assert.Equal("localhost", name); - selectorCalled++; - return _x509Certificate2; - } - }) - } - }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); + Assert.Equal("localhost", name); + selectorCalled++; + return _x509Certificate2; + } + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -209,28 +190,26 @@ public async Task UsesProvidedServerCertificateSelector() public async Task UsesProvidedServerCertificateSelectorEachTime() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (connection, name) => { - ServerCertificateSelector = (connection, name) => + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); + Assert.Equal("localhost", name); + selectorCalled++; + if (selectorCalled == 1) { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); - Assert.Equal("localhost", name); - selectorCalled++; - if (selectorCalled == 1) - { - return _x509Certificate2; - } - return _x509Certificate2NoExt; + return _x509Certificate2; } - }) - } - }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + return _x509Certificate2NoExt; + } + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -259,21 +238,19 @@ public async Task UsesProvidedServerCertificateSelectorEachTime() public async Task UsesProvidedServerCertificateSelectorValidatesEkus() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (features, name) => { - ServerCertificateSelector = (features, name) => - { - selectorCalled++; - return TestResources.GetTestCertificate("eku.code_signing.pfx"); - } - }) - } - }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return TestResources.GetTestCertificate("eku.code_signing.pfx"); + } + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -292,25 +269,23 @@ await Assert.ThrowsAsync(() => public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificate() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificate = _x509Certificate2NoExt, + ServerCertificateSelector = (connection, name) => { - ServerCertificate = _x509Certificate2NoExt, - ServerCertificateSelector = (connection, name) => - { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); - Assert.Equal("localhost", name); - selectorCalled++; - return _x509Certificate2; - } - }) - } - }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); + Assert.Equal("localhost", name); + selectorCalled++; + return _x509Certificate2; + } + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -329,21 +304,19 @@ public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificat public async Task UsesProvidedServerCertificateSelectorFailsIfYouReturnNull() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (features, name) => { - ServerCertificateSelector = (features, name) => - { - selectorCalled++; - return null; - } - }) - } - }; - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return null; + } + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -363,19 +336,16 @@ await Assert.ThrowsAsync(() => [InlineData(HttpProtocols.Http1AndHttp2)] // Make sure Http/1.1 doesn't regress with Http/2 enabled. public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - Protocols = httpProtocols, - ConnectionAdapters = + listenOptions.Protocols = httpProtocols; + listenOptions.UseHttps(options => { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) - } - }; + options.ServerCertificate = _x509Certificate2; + options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + options.AllowAnyClientCertificate(); + }); + } await using (var server = new TestServer(context => { @@ -384,7 +354,7 @@ public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) Assert.NotNull(tlsFeature.ClientCertificate); Assert.NotNull(context.Connection.ClientCertificate); return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -401,15 +371,12 @@ public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) [Fact] public async Task HttpsSchemePassedToRequestFeature() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); + } - await using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("https", result); @@ -419,20 +386,18 @@ public async Task HttpsSchemePassedToRequestFeature() [Fact] public async Task DoesNotSupportTls10() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(options => { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) - } - }; + options.ServerCertificate = _x509Certificate2; + options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + options.AllowAnyClientCertificate(); + }); + } - await using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), new TestServiceContext(LoggerFactory), listenOptions)) + + await using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { // SslStream is used to ensure the certificate is actually passed to the server // HttpClient might not send the certificate because it is invalid or it doesn't match any @@ -453,26 +418,23 @@ public async Task DoesNotSupportTls10() public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(ClientCertificateMode mode) { var clientCertificateValidationCalled = false; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => - { - clientCertificateValidationCalled = true; - Assert.NotNull(certificate); - Assert.NotNull(chain); - return true; - } - }) - } - }; + clientCertificateValidationCalled = true; + Assert.NotNull(certificate); + Assert.NotNull(chain); + return true; + } + }); + } - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -490,20 +452,17 @@ public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(Cli [InlineData(ClientCertificateMode.RequireCertificate)] public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false + }); + } - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -519,19 +478,16 @@ public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode) [InlineData(ClientCertificateMode.RequireCertificate)] public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCertificateMode mode) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode + }); + } - await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -543,20 +499,42 @@ public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCerti } [Fact] - public async Task CertificatePassedToHttpContextIsNotDisposed() + public async Task AllowAnyCertOverridesCertificateValidation() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(options => { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) + options.ServerCertificate = _x509Certificate2; + options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + options.ClientCertificateValidation = (certificate, x509Chain, sslPolicyErrors) => false; + options.AllowAnyClientCertificate(); + }); + } + + await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) + { + using (var connection = server.CreateConnection()) + { + var stream = OpenSslStream(connection.Stream); + await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false); + await AssertConnectionResult(stream, true); } - }; + } + } + + [Fact] + public async Task CertificatePassedToHttpContextIsNotDisposed() + { + void ConfigureListenOptions(ListenOptions listenOptions) + { + listenOptions.UseHttps(options => + { + options.ServerCertificate = _x509Certificate2; + options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + options.AllowAnyClientCertificate(); + }); + } RequestDelegate app = context => { @@ -568,7 +546,7 @@ public async Task CertificatePassedToHttpContextIsNotDisposed() return context.Response.WriteAsync("hello world"); }; - await using (var server = new TestServer(app, new TestServiceContext(LoggerFactory), listenOptions)) + await using (var server = new TestServer(app, new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, ConfigureListenOptions)) { // SslStream is used to ensure the certificate is actually passed to the server // HttpClient might not send the certificate because it is invalid or it doesn't match any @@ -591,7 +569,7 @@ public void AcceptsCertificateWithoutExtensions(string testCertName) var cert = new X509Certificate2(certPath, "testPassword"); Assert.Empty(cert.Extensions.OfType()); - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, }); @@ -609,7 +587,7 @@ public void ValidatesEnhancedKeyUsageOnCertificate(string testCertName) var eku = Assert.Single(cert.Extensions.OfType()); Assert.NotEmpty(eku.EnhancedKeyUsages); - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, }); @@ -628,7 +606,7 @@ public void ThrowsForCertificatesMissingServerEku(string testCertName) Assert.NotEmpty(eku.EnhancedKeyUsages); var ex = Assert.Throws(() => - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, })); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index 00602983e4b9..87d0316d7aa9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -122,7 +122,7 @@ public async Task EmptyRequestLoggedAsDebug() LoggerFactory.AddProvider(loggerProvider); await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -149,7 +149,7 @@ public async Task ClientHandshakeFailureLoggedAsDebug() LoggerFactory.AddProvider(loggerProvider); await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -193,7 +193,7 @@ public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort() } } }, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -237,7 +237,7 @@ public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnecti tcs.SetException(ex); } }, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -268,7 +268,7 @@ public async Task DoesNotThrowObjectDisposedExceptionOnEmptyConnection() LoggerFactory.AddProvider(loggerProvider); await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -294,7 +294,7 @@ public async Task ConnectionFilterDoesNotLeakBlock() LoggerFactory.AddProvider(loggerProvider); await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -313,7 +313,7 @@ public async Task HandshakeTimesOutAndIsLoggedAsDebug() var loggerProvider = new HandshakeErrorLoggerProvider(); LoggerFactory.AddProvider(loggerProvider); - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }; var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var handshakeStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -326,7 +326,10 @@ public async Task HandshakeTimesOutAndIsLoggedAsDebug() listenOptions.UseHttps(o => { o.ServerCertificate = new X509Certificate2(TestResources.GetTestCertificate()); - o.OnAuthenticate = (_, __) => handshakeStartedTcs.SetResult(null); + o.OnAuthenticate = (_, __) => + { + handshakeStartedTcs.SetResult(null); + }; handshakeTimeout = o.HandshakeTimeout; }); @@ -358,7 +361,7 @@ public async Task ClientAttemptingToUseUnsupportedProtocolIsLoggedAsDebug() LoggerFactory.AddProvider(loggerProvider); await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(TestResources.GetTestCertificate()); @@ -390,7 +393,7 @@ public async Task OnAuthenticate_SeesOtherSettings() var onAuthenticateCalled = false; await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(httpsOptions => @@ -426,7 +429,7 @@ public async Task OnAuthenticate_CanSetSettings() var onAuthenticateCalled = false; await using (var server = new TestServer(context => Task.CompletedTask, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 1 }, listenOptions => { listenOptions.UseHttps(httpsOptions => @@ -462,7 +465,7 @@ private class HandshakeErrorLoggerProvider : ILoggerProvider public ILogger CreateLogger(string categoryName) { - if (categoryName == TypeNameHelper.GetTypeDisplayName(typeof(HttpsConnectionAdapter))) + if (categoryName == TypeNameHelper.GetTypeDisplayName(typeof(HttpsConnectionMiddleware))) { return FilterLogger; } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs index 4fe77ba7d347..42e98c878ab3 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/LoggingConnectionAdapterTests.cs @@ -23,7 +23,7 @@ public async Task LoggingConnectionAdapterCanBeAddedBeforeAndAfterHttpsAdapter() context.Response.ContentLength = 12; return context.Response.WriteAsync("Hello World!"); }, - new TestServiceContext(LoggerFactory), + new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = 3 }, listenOptions => { listenOptions.UseConnectionLogging(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs index f0603fef3a15..f01dd716338d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs @@ -18,17 +18,14 @@ public class ResponseDrainingTests : TestApplicationErrorLoggerLoggedTest public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), - new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) - { - ConnectionAdapters = { new PassThroughConnectionAdapter() } - } + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)).UsePassThrough() }; [Theory] [MemberData(nameof(ConnectionAdapterData))] public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(LoggerFactory) { ExpectedConnectionMiddlewareCount = listenOptions._middleware.Count }; var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2)); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs index f698a4908d02..b8e3209f8887 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/InMemoryConnection.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTrans internal class InMemoryConnection : StreamBackedTestConnection { public InMemoryConnection(InMemoryTransportConnection transportConnection) - : base(new RawStream(transportConnection.Output, transportConnection.Input)) + : base(new DuplexPipeStream(transportConnection.Output, transportConnection.Input)) { TransportConnection = transportConnection; } diff --git a/src/Servers/testassets/ServerComparison.TestSites/OneTransformPerRequest.cs b/src/Servers/testassets/ServerComparison.TestSites/OneTransformPerRequest.cs new file mode 100644 index 000000000000..47d392d9d66c --- /dev/null +++ b/src/Servers/testassets/ServerComparison.TestSites/OneTransformPerRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; + +namespace ServerComparison.TestSites +{ + public class OneTransformPerRequest : IClaimsTransformation + { + public OneTransformPerRequest(IHttpContextAccessor contextAccessor) + { + ContextAccessor = contextAccessor; + } + + public IHttpContextAccessor ContextAccessor { get; } + + public Task TransformAsync(ClaimsPrincipal principal) + { + var context = ContextAccessor.HttpContext; + if (context.Items["Transformed"] != null) + { + throw new InvalidOperationException("Transformation ran multiple times."); + } + context.Items["Transformed"] = true; + return Task.FromResult(principal); + } + } +} diff --git a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs index 73810a6b80a5..5528d19578ca 100644 --- a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs +++ b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs @@ -25,6 +25,8 @@ public StartupNtlmAuthentication(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { + services.AddHttpContextAccessor(); + services.AddSingleton(); if (IsKestrel) { services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs index 979ab5d2d7f5..6ccec330c730 100644 --- a/src/Shared/WebEncoders/WebEncoders.cs +++ b/src/Shared/WebEncoders/WebEncoders.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +#if NETCOREAPP3_0 using System.Buffers; +#endif using System.Diagnostics; using System.Globalization; using Microsoft.Extensions.WebEncoders.Sources; diff --git a/src/SignalR/Directory.Build.props b/src/SignalR/Directory.Build.props index 6269827f5362..27a3751cd887 100644 --- a/src/SignalR/Directory.Build.props +++ b/src/SignalR/Directory.Build.props @@ -6,6 +6,11 @@ $(MSBuildThisFileDirectory)common\testassets\Tests.Utils\Microsoft.AspNetCore.SignalR.Tests.Utils.csproj + + + 0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7 + + $(NoWarn);CS1591 true diff --git a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj index 2c0e6d360949..0bc1fe9403aa 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj +++ b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj @@ -1,4 +1,4 @@ - + Client for ASP.NET Core SignalR @@ -37,4 +37,9 @@ + + + + + diff --git a/src/SignalR/clients/csharp/Client.Core/src/Properties/AssemblyInfo.cs b/src/SignalR/clients/csharp/Client.Core/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 8376590f7650..000000000000 --- a/src/SignalR/clients/csharp/Client.Core/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Client.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj b/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj index 846dbfa8a77d..f804da1a265b 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Microsoft.AspNetCore.Http.Connections.Client.csproj @@ -1,4 +1,4 @@ - + Client for ASP.NET Core Connection Handlers @@ -19,4 +19,11 @@ + + + + + + + diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Properties/AssemblyInfo.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 08686a03d4b4..000000000000 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Client.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Microbenchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java index 15d85d63d762..4cd5f68263ae 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java @@ -11,7 +11,7 @@ final class CompletionMessage extends HubMessage { public CompletionMessage(String invocationId, Object result, String error) { if (error != null && result != null) { - throw new IllegalArgumentException("Expected either 'error' or 'result' to be provided, but not both"); + throw new IllegalArgumentException("Expected either 'error' or 'result' to be provided, but not both."); } this.invocationId = invocationId; this.result = result; @@ -34,4 +34,4 @@ public String getInvocationId() { public HubMessageType getMessageType() { return HubMessageType.values()[type - 1]; } -} \ No newline at end of file +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index b3b564b7112e..67669576e74d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -591,10 +591,42 @@ Object[] checkUploadStream(Object[] args, List streamIds) { this.streamMap.put(streamId, stream); } } - + return params.toArray(); } + + /** + * Invokes a hub method on the server using the specified method name and arguments. + * + * @param method The name of the server method to invoke. + * @param args The arguments used to invoke the server method. + * @return A Completable that indicates when the invocation has completed. + */ + @SuppressWarnings("unchecked") + public Completable invoke(String method, Object... args) { + if (hubConnectionState != HubConnectionState.CONNECTED) { + throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); + } + + String id = connectionState.getNextInvocationId(); + + CompletableSubject subject = CompletableSubject.create(); + InvocationRequest irq = new InvocationRequest(null, id); + connectionState.addInvocation(irq); + + Subject pendingCall = irq.getPendingCall(); + + pendingCall.subscribe(result -> subject.onComplete(), + error -> subject.onError(error), + () -> subject.onComplete()); + + // Make sure the actual send is after setting up the callbacks otherwise there is a race + // where the map doesn't have the callbacks yet when the response is returned + sendInvocationMessage(method, args, id, false); + return subject; + } + /** * Invokes a hub method on the server using the specified method name and arguments. * diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index b85b502fb305..b5104919ab44 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; +import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.disposables.Disposable; @@ -163,7 +164,6 @@ public void changingUrlWhenConnectedThrows() { assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); assertEquals("http://example.com", hubConnection.getBaseUrl()); - Throwable exception = assertThrows(IllegalStateException.class, () -> hubConnection.setBaseUrl("http://newurl.com")); assertEquals("The HubConnection must be in the disconnected state to change the url.",exception.getMessage()); } @@ -946,15 +946,100 @@ public void invokeWaitsForCompletionMessage() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(Integer.class, "echo", "message"); - result.doOnSuccess(value -> done.set(true)); + result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } + + @Test + public void invokeNoReturnValueWaitsForCompletion() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(); + + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertFalse(done.get()); + + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\"}" + RECORD_SEPARATOR); + + assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } + + @Test + public void invokeCompletedByCompletionMessageWithResult() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(); + + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertFalse(done.get()); + + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); + + assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } + + @Test + public void completionWithResultAndErrorHandlesError() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); + + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertFalse(done.get()); + + Throwable exception = assertThrows(IllegalArgumentException.class, () -> mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"error\":\"There was an error\"}" + RECORD_SEPARATOR)); + assertEquals("Expected either 'error' or 'result' to be provided, but not both.", exception.getMessage()); } - + + @Test + public void invokeNoReturnValueHandlesError() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); + + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertFalse(done.get()); + + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"error\":\"There was an error\"}" + RECORD_SEPARATOR); + + result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet(); + + AtomicReference errorMessage = new AtomicReference<>(); + result.doOnError(error -> { + errorMessage.set(error.getMessage()); + }).subscribe(() -> {}, (error) -> {}); + + assertEquals("There was an error", errorMessage.get()); + } + @Test public void canSendNullArgInInvocation() { MockTransport mockTransport = new MockTransport(); @@ -964,13 +1049,14 @@ public void canSendNullArgInInvocation() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(String.class, "fixedMessage", null); - result.doOnSuccess(value -> done.set(true)); + result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); } @Test @@ -982,13 +1068,14 @@ public void canSendMultipleNullArgsInInvocation() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(String.class, "fixedMessage", null, null); - result.doOnSuccess(value -> done.set(true)); + result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null,null]}"+ RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); } @Test @@ -1002,8 +1089,8 @@ public void multipleInvokesWaitForOwnCompletionMessage() { AtomicBoolean doneSecond = new AtomicBoolean(); Single result = hubConnection.invoke(Integer.class, "echo", "message"); Single result2 = hubConnection.invoke(String.class, "echo", "message"); - result.doOnSuccess(value -> doneFirst.set(true)); - result2.doOnSuccess(value -> doneSecond.set(true)); + result.doOnSuccess(value -> doneFirst.set(true)).subscribe(); + result2.doOnSuccess(value -> doneSecond.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[2]); assertFalse(doneFirst.get()); @@ -1012,9 +1099,11 @@ public void multipleInvokesWaitForOwnCompletionMessage() { mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"2\",\"result\":\"message\"}" + RECORD_SEPARATOR); assertEquals("message", result2.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertFalse(doneFirst.get()); + assertTrue(doneSecond.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(doneFirst.get()); } @Test @@ -1028,12 +1117,13 @@ public void invokeWorksForPrimitiveTypes() { // int.class is a primitive type and since we use Class.cast to cast an Object to the expected return type // which does not work for primitives we have to write special logic for that case. Single result = hubConnection.invoke(int.class, "echo", "message"); - result.doOnSuccess(value -> done.set(true)); + result.doOnSuccess(value -> done.set(true)).subscribe(); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); } @Test diff --git a/src/SignalR/clients/ts/client-ts.npmproj b/src/SignalR/clients/ts/client-ts.npmproj index f3aa6f943654..ab28018de9de 100644 --- a/src/SignalR/clients/ts/client-ts.npmproj +++ b/src/SignalR/clients/ts/client-ts.npmproj @@ -4,7 +4,7 @@ false false - true + false diff --git a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp3.0.cs b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp3.0.cs index 978e249509cc..64b628f6a430 100644 --- a/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp3.0.cs +++ b/src/SignalR/common/Http.Connections/ref/Microsoft.AspNetCore.Http.Connections.netcoreapp3.0.cs @@ -61,11 +61,6 @@ public partial class NegotiateMetadata { public NegotiateMetadata() { } } - public static partial class ServerSentEventsMessageFormatter - { - [System.Diagnostics.DebuggerStepThroughAttribute] - public static System.Threading.Tasks.Task WriteMessageAsync(System.Buffers.ReadOnlySequence payload, System.IO.Stream output) { throw null; } - } public partial class WebSocketOptions { public WebSocketOptions() { } diff --git a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj index 175167df71a8..1504fac39f9c 100644 --- a/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj @@ -1,4 +1,4 @@ - + Components for providing real-time bi-directional communication across the Web. @@ -34,4 +34,8 @@ + + + + diff --git a/src/SignalR/common/Http.Connections/src/Properties/AssemblyInfo.cs b/src/SignalR/common/Http.Connections/src/Properties/AssemblyInfo.cs deleted file mode 100644 index b849e763b621..000000000000 --- a/src/SignalR/common/Http.Connections/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Http.Connections.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/SignalR/common/Http.Connections/src/ServerSentEventsMessageFormatter.cs b/src/SignalR/common/Http.Connections/src/ServerSentEventsMessageFormatter.cs index 2f69e62aa6d2..efd2e24f0f90 100644 --- a/src/SignalR/common/Http.Connections/src/ServerSentEventsMessageFormatter.cs +++ b/src/SignalR/common/Http.Connections/src/ServerSentEventsMessageFormatter.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Http.Connections { - public static class ServerSentEventsMessageFormatter + internal static class ServerSentEventsMessageFormatter { private static readonly byte[] DataPrefix = { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' }; private static readonly byte[] Newline = { (byte)'\r', (byte)'\n' }; diff --git a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj index d1ba59a3eb89..118f42dc2385 100644 --- a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj @@ -36,4 +36,9 @@ + + + + + diff --git a/src/SignalR/common/SignalR.Common/src/Properties/AssemblyInfo.cs b/src/SignalR/common/SignalR.Common/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 0758e9ccfc2a..000000000000 --- a/src/SignalR/common/SignalR.Common/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Common.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests.Utils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs b/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs index 79fb05425cfc..2d8a1840ad08 100644 --- a/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs +++ b/src/SignalR/common/testassets/Tests.Utils/HubConnectionContextUtils.cs @@ -20,7 +20,12 @@ static class HubConnectionContextUtils { public static HubConnectionContext Create(ConnectionContext connection, IHubProtocol protocol = null, string userIdentifier = null) { - return new HubConnectionContext(connection, TimeSpan.FromSeconds(15), NullLoggerFactory.Instance) + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.FromSeconds(15), + }; + + return new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance) { Protocol = protocol ?? new JsonHubProtocol(), UserIdentifier = userIdentifier, @@ -29,15 +34,20 @@ public static HubConnectionContext Create(ConnectionContext connection, IHubProt public static MockHubConnectionContext CreateMock(ConnectionContext connection) { - return new MockHubConnectionContext(connection, TimeSpan.FromSeconds(15), NullLoggerFactory.Instance, TimeSpan.FromSeconds(15), streamBufferCapacity: 10); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.FromSeconds(15), + ClientTimeoutInterval = TimeSpan.FromSeconds(15), + StreamBufferCapacity = 10, + }; + return new MockHubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); } public class MockHubConnectionContext : HubConnectionContext { - public MockHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval, int streamBufferCapacity) - : base(connectionContext, keepAliveInterval, loggerFactory, clientTimeoutInterval, streamBufferCapacity) + public MockHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) + : base(connectionContext, contextOptions, loggerFactory) { - } public override ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken = default) diff --git a/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs b/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs index c074c1c21d72..8e1b88eccc98 100644 --- a/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/BroadcastBenchmark.cs @@ -46,7 +46,11 @@ public void GlobalSetup() { var pair = DuplexPipe.CreateConnectionPair(options, options); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport); - var hubConnection = new HubConnectionContext(connection, Timeout.InfiniteTimeSpan, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = Timeout.InfiniteTimeSpan, + }; + var hubConnection = new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); hubConnection.Protocol = protocol; _hubLifetimeManager.OnConnectedAsync(hubConnection).GetAwaiter().GetResult(); _hubLifetimeManager.AddToGroupAsync(connection.ConnectionId, TestGroupName).GetAwaiter().GetResult(); diff --git a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs index 956a298ed975..982ead5340b6 100644 --- a/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/DefaultHubDispatcherBenchmark.cs @@ -4,9 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.IO; using System.IO.Pipelines; -using System.Reactive.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -46,7 +44,11 @@ public void GlobalSetup() var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport); - _connectionContext = new NoErrorHubConnectionContext(connection, TimeSpan.Zero, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.Zero, + }; + _connectionContext = new NoErrorHubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); _connectionContext.Protocol = new FakeHubProtocol(); } @@ -83,7 +85,8 @@ public class NoErrorHubConnectionContext : HubConnectionContext { public TaskCompletionSource ReceivedCompleted = new TaskCompletionSource(); - public NoErrorHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) : base(connectionContext, keepAliveInterval, loggerFactory) + public NoErrorHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) + : base(connectionContext, contextOptions, loggerFactory) { } diff --git a/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs b/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs index d54311a7137f..1a2210993897 100644 --- a/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/DefaultHubLifetimeManagerBenchmark.cs @@ -51,7 +51,11 @@ public void GlobalSetup() ConnectionId = connectionId, Transport = new TestDuplexPipe(ForceAsync) }; - var hubConnectionContext = new HubConnectionContext(connectionContext, TimeSpan.Zero, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = TimeSpan.Zero, + }; + var hubConnectionContext = new HubConnectionContext(connectionContext, contextOptions, NullLoggerFactory.Instance); hubConnectionContext.UserIdentifier = userIdentifier; hubConnectionContext.Protocol = jsonHubProtocol; diff --git a/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs b/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs index 891a73b49e2e..a1ca8995782b 100644 --- a/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs +++ b/src/SignalR/perf/Microbenchmarks/HubConnectionContextBenchmark.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared; @@ -44,7 +43,11 @@ public void GlobalSetup() _pipe = new TestDuplexPipe(); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), _pipe, _pipe); - _hubConnectionContext = new HubConnectionContext(connection, Timeout.InfiniteTimeSpan, NullLoggerFactory.Instance); + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = Timeout.InfiniteTimeSpan, + }; + _hubConnectionContext = new HubConnectionContext(connection, contextOptions, NullLoggerFactory.Instance); _successHubProtocolResolver = new TestHubProtocolResolver(new NewtonsoftJsonHubProtocol()); _failureHubProtocolResolver = new TestHubProtocolResolver(null); diff --git a/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj index 8011d5de2bbd..c007a68669ab 100644 --- a/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj +++ b/src/SignalR/perf/Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -10,6 +10,7 @@ + diff --git a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs index 0d36db60a634..14087c25fe1f 100644 --- a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs +++ b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs @@ -127,9 +127,7 @@ public static partial class HubClientsExtensions } public partial class HubConnectionContext { - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.TimeSpan clientTimeoutInterval) { } - public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, System.TimeSpan keepAliveInterval, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.TimeSpan clientTimeoutInterval, int streamBufferCapacity) { } + public HubConnectionContext(Microsoft.AspNetCore.Connections.ConnectionContext connectionContext, Microsoft.AspNetCore.SignalR.HubConnectionContextOptions contextOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public virtual System.Threading.CancellationToken ConnectionAborted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public virtual string ConnectionId { get { throw null; } } public virtual Microsoft.AspNetCore.Http.Features.IFeatureCollection Features { get { throw null; } } @@ -141,6 +139,13 @@ public virtual void Abort() { } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.Protocol.HubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.ValueTask WriteAsync(Microsoft.AspNetCore.SignalR.SerializedHubMessage message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + public partial class HubConnectionContextOptions + { + public HubConnectionContextOptions() { } + public System.TimeSpan ClientTimeoutInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int StreamBufferCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } public partial class HubConnectionHandler : Microsoft.AspNetCore.Connections.ConnectionHandler where THub : Microsoft.AspNetCore.SignalR.Hub { public HubConnectionHandler(Microsoft.AspNetCore.SignalR.HubLifetimeManager lifetimeManager, Microsoft.AspNetCore.SignalR.IHubProtocolResolver protocolResolver, Microsoft.Extensions.Options.IOptions globalHubOptions, Microsoft.Extensions.Options.IOptions> hubOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.IUserIdProvider userIdProvider, Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory) { } diff --git a/src/SignalR/server/Core/src/HubConnectionContext.cs b/src/SignalR/server/Core/src/HubConnectionContext.cs index f5f870b38bcb..16ca9313bf25 100644 --- a/src/SignalR/server/Core/src/HubConnectionContext.cs +++ b/src/SignalR/server/Core/src/HubConnectionContext.cs @@ -48,37 +48,17 @@ public class HubConnectionContext /// Initializes a new instance of the class. /// /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. /// The logger factory. - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) - : this(connectionContext, keepAliveInterval, loggerFactory, HubOptionsSetup.DefaultClientTimeoutInterval, HubOptionsSetup.DefaultStreamBufferCapacity) { } - - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. - /// The logger factory. - /// Clients we haven't heard from in this interval are assumed to have disconnected. - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval) - : this(connectionContext, keepAliveInterval, loggerFactory, clientTimeoutInterval, HubOptionsSetup.DefaultStreamBufferCapacity) { } - - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - /// The keep alive interval. If no messages are sent by the server in this interval, a Ping message will be sent. - /// The logger factory. - /// Clients we haven't heard from in this interval are assumed to have disconnected. - /// The buffer size for client upload streams - public HubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory, TimeSpan clientTimeoutInterval, int streamBufferCapacity) + /// The options to configure the HubConnectionContext. + public HubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory) { + _keepAliveInterval = contextOptions.KeepAliveInterval.Ticks; + _clientTimeoutInterval = contextOptions.ClientTimeoutInterval.Ticks; + _streamBufferCapacity = contextOptions.StreamBufferCapacity; + _connectionContext = connectionContext; _logger = loggerFactory.CreateLogger(); ConnectionAborted = _connectionAbortedTokenSource.Token; - _keepAliveInterval = keepAliveInterval.Ticks; - _clientTimeoutInterval = clientTimeoutInterval.Ticks; - _streamBufferCapacity = streamBufferCapacity; } internal StreamTracker StreamTracker diff --git a/src/SignalR/server/Core/src/HubConnectionContextOptions.cs b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs new file mode 100644 index 000000000000..a38096cbefea --- /dev/null +++ b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.SignalR +{ + /// + /// Options used to configure . + /// + public class HubConnectionContextOptions + { + /// + /// Gets or sets the interval used to send keep alive pings to connected clients. + /// + public TimeSpan KeepAliveInterval { get; set; } + + /// + /// Gets or sets the time window clients have to send a message before the server closes the connection. + /// + public TimeSpan ClientTimeoutInterval { get; set; } + + /// + /// Gets or sets the max buffer size for client upload streams. + /// + public int StreamBufferCapacity { get; set; } + } +} diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index d3e2b4185c52..252f7436da39 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR @@ -75,21 +74,26 @@ IServiceScopeFactory serviceScopeFactory public override async Task OnConnectedAsync(ConnectionContext connection) { // We check to see if HubOptions are set because those take precedence over global hub options. - // Then set the keepAlive and handshakeTimeout values to the defaults in HubOptionsSetup incase they were explicitly set to null. - var keepAlive = _hubOptions.KeepAliveInterval ?? _globalHubOptions.KeepAliveInterval ?? HubOptionsSetup.DefaultKeepAliveInterval; - var clientTimeout = _hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval; - var handshakeTimeout = _hubOptions.HandshakeTimeout ?? _globalHubOptions.HandshakeTimeout ?? HubOptionsSetup.DefaultHandshakeTimeout; - var streamBufferCapacity = _hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? HubOptionsSetup.DefaultStreamBufferCapacity; - var supportedProtocols = _hubOptions.SupportedProtocols ?? _globalHubOptions.SupportedProtocols; + // Then set the keepAlive and handshakeTimeout values to the defaults in HubOptionsSetup when they were explicitly set to null. - if (supportedProtocols != null && supportedProtocols.Count == 0) + var supportedProtocols = _hubOptions.SupportedProtocols ?? _globalHubOptions.SupportedProtocols; + if (supportedProtocols == null || supportedProtocols.Count == 0) { throw new InvalidOperationException("There are no supported protocols"); } + var handshakeTimeout = _hubOptions.HandshakeTimeout ?? _globalHubOptions.HandshakeTimeout ?? HubOptionsSetup.DefaultHandshakeTimeout; + + var contextOptions = new HubConnectionContextOptions() + { + KeepAliveInterval = _hubOptions.KeepAliveInterval ?? _globalHubOptions.KeepAliveInterval ?? HubOptionsSetup.DefaultKeepAliveInterval, + ClientTimeoutInterval = _hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval, + StreamBufferCapacity = _hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? HubOptionsSetup.DefaultStreamBufferCapacity, + }; + Log.ConnectedStarting(_logger); - var connectionContext = new HubConnectionContext(connection, keepAlive, _loggerFactory, clientTimeout, streamBufferCapacity); + var connectionContext = new HubConnectionContext(connection, contextOptions, _loggerFactory); var resolvedSupportedProtocols = (supportedProtocols as IReadOnlyList) ?? supportedProtocols.ToList(); if (!await connectionContext.HandshakeAsync(handshakeTimeout, resolvedSupportedProtocols, _protocolResolver, _userIdProvider, _enableDetailedErrors)) diff --git a/src/SignalR/server/Core/src/HubOptions.cs b/src/SignalR/server/Core/src/HubOptions.cs index 7c49fd2957a8..e92a3686ca46 100644 --- a/src/SignalR/server/Core/src/HubOptions.cs +++ b/src/SignalR/server/Core/src/HubOptions.cs @@ -17,7 +17,7 @@ public class HubOptions // for all available protocols. /// - /// Gets or sets the interval used by the server to timeout incoming handshake requests by clients. The default timeout is 15 seconds + /// Gets or sets the interval used by the server to timeout incoming handshake requests by clients. The default timeout is 15 seconds. /// public TimeSpan? HandshakeTimeout { get; set; } = null; diff --git a/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj b/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj index eeb0e55e0e1f..e0c5505ef026 100644 --- a/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj +++ b/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj @@ -23,4 +23,11 @@ + + + + + + + diff --git a/src/SignalR/server/Core/src/Properties/AssemblyInfo.cs b/src/SignalR/server/Core/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 95d162c8cee0..000000000000 --- a/src/SignalR/server/Core/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests.Utils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Microbenchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index 25e47f03f4db..56b77c0b7a50 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -2309,6 +2309,54 @@ public async Task HubOptionsCanUseCustomMessagePackSettings() } } + [Fact] + public async Task HubOptionsCanNotHaveNullSupportedProtocols() + { + using (StartVerifiableLog()) + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => + { + services.AddSignalR(o => + { + o.SupportedProtocols = null; + }); + }, LoggerFactory); + + var connectionHandler = serviceProvider.GetService>(); + + var msgPackOptions = serviceProvider.GetRequiredService>(); + using (var client = new TestClient(protocol: new MessagePackHubProtocol(msgPackOptions))) + { + client.SupportedFormats = TransferFormat.Binary; + await Assert.ThrowsAsync(async () => await await client.ConnectAsync(connectionHandler, expectedHandshakeResponseMessage: false)).OrTimeout(); + } + } + } + + [Fact] + public async Task HubOptionsCanNotHaveEmptySupportedProtocols() + { + using (StartVerifiableLog()) + { + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => + { + services.AddSignalR(o => + { + o.SupportedProtocols = new List(); + }); + }, LoggerFactory); + + var connectionHandler = serviceProvider.GetService>(); + + var msgPackOptions = serviceProvider.GetRequiredService>(); + using (var client = new TestClient(protocol: new MessagePackHubProtocol(msgPackOptions))) + { + client.SupportedFormats = TransferFormat.Binary; + await Assert.ThrowsAsync(async () => await await client.ConnectAsync(connectionHandler, expectedHandshakeResponseMessage: false)).OrTimeout(); + } + } + } + [Fact] public async Task ConnectionUserIdIsAssignedByUserIdProvider() { diff --git a/src/SignalR/server/StackExchangeRedis/src/AssemblyInfo.cs b/src/SignalR/server/StackExchangeRedis/src/AssemblyInfo.cs deleted file mode 100644 index 84cff6788523..000000000000 --- a/src/SignalR/server/StackExchangeRedis/src/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.SignalR.Microbenchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj b/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj index c90014cd8575..21e567d8a696 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj +++ b/src/SignalR/server/StackExchangeRedis/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj @@ -18,4 +18,9 @@ + + + + +