From 75dcbf3f98a12c98a2c33b89988af1f770e1c52a Mon Sep 17 00:00:00 2001 From: Jacques Eloff Date: Thu, 16 May 2019 10:32:37 -0700 Subject: [PATCH 01/41] Fix .wxl reference --- src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs | 2 +- src/Installers/Windows/WindowsHostingBundle/Bundle.wxs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs b/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs index 4880a599b7..ecdb8d7264 100644 --- a/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs +++ b/src/Installers/Windows/SharedFrameworkBundle/Bundle.wxs @@ -26,7 +26,7 @@ - + diff --git a/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs b/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs index 95897534b7..8fd846a61a 100644 --- a/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs +++ b/src/Installers/Windows/WindowsHostingBundle/Bundle.wxs @@ -21,7 +21,7 @@ - + From 595660984652ef857bbdcc9f1d8a8921b88601d7 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Thu, 16 May 2019 16:12:18 -0700 Subject: [PATCH 02/41] start of request queue --- eng/ProjectReferences.props | 1 + .../Microsoft.AspNetCore.RequestQueue.csproj | 14 +++++ ...t.AspNetCore.RequestQueue.netcoreapp3.0.cs | 51 +++++++++++++++++ .../sample/RequestQueueSample.csproj | 13 +++++ src/Middleware/RequestQueue/sample/Startup.cs | 50 +++++++++++++++++ .../Microsoft.AspNetCore.RequestQueue.csproj | 19 +++++++ .../RequestQueue/src/SemaphoreWrapper.cs | 33 +++++++++++ ...osoft.AspNetCore.RequestQueue.Tests.csproj | 10 ++++ .../test/SemaphoreWrapperTests.cs | 56 +++++++++++++++++++ src/Middleware/build.cmd | 3 + 10 files changed, 250 insertions(+) create mode 100644 src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj create mode 100644 src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs create mode 100644 src/Middleware/RequestQueue/sample/RequestQueueSample.csproj create mode 100644 src/Middleware/RequestQueue/sample/Startup.cs create mode 100644 src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj create mode 100644 src/Middleware/RequestQueue/src/SemaphoreWrapper.cs create mode 100644 src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj create mode 100644 src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs create mode 100644 src/Middleware/build.cmd diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 067ba79788..e274d05a71 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -79,6 +79,7 @@ + diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj new file mode 100644 index 0000000000..05f6a42637 --- /dev/null +++ b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp3.0 + + + + + + + + + + diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs new file mode 100644 index 0000000000..dc22bca076 --- /dev/null +++ b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs @@ -0,0 +1,51 @@ +// 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.Builder +{ + public static partial class HstsBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHsts(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } + public static partial class HstsServicesExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHsts(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } + } + public static partial class HttpsPolicyBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHttpsRedirection(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } + public static partial class HttpsRedirectionServicesExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpsRedirection(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } + } +} +namespace Microsoft.AspNetCore.HttpsPolicy +{ + public partial class HstsMiddleware + { + public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options) { } + public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } + public partial class HstsOptions + { + public HstsOptions() { } + public System.Collections.Generic.IList ExcludedHosts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool IncludeSubDomains { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan MaxAge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool Preload { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class HttpsRedirectionMiddleware + { + public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature serverAddressesFeature) { } + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } + public partial class HttpsRedirectionOptions + { + public HttpsRedirectionOptions() { } + public int? HttpsPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int RedirectStatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} diff --git a/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj b/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj new file mode 100644 index 0000000000..57892c2249 --- /dev/null +++ b/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.0 + + + + + + + + + diff --git a/src/Middleware/RequestQueue/sample/Startup.cs b/src/Middleware/RequestQueue/sample/Startup.cs new file mode 100644 index 0000000000..5768d045e6 --- /dev/null +++ b/src/Middleware/RequestQueue/sample/Startup.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace RequestQueueSample +{ + public class Startup + { + // 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) + { + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) + { + app.Run(async context => + { + await context.Response.WriteAsync("Hello world!"); + }); + } + + // Entry point for the application. + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file + .ConfigureLogging(factory => + { + factory.SetMinimumLevel(LogLevel.Debug); + factory.AddConsole(); + }) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj new file mode 100644 index 0000000000..ddc77f3682 --- /dev/null +++ b/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj @@ -0,0 +1,19 @@ + + + + ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. + netcoreapp3.0 + $(NoWarn);CS1591 + true + true + $(WarningsNotAsErrors);CS1591 + + + + + + + + + + diff --git a/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs b/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs new file mode 100644 index 0000000000..aff2f66919 --- /dev/null +++ b/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.RequestQueue +{ + internal class SemaphoreWrapper + { + private static SemaphoreSlim semaphore; + + public SemaphoreWrapper(int qlength) + { + semaphore = new SemaphoreSlim(qlength); + } + + public Task EnterQueue() + { + return semaphore.WaitAsync(); + } + + public void LeaveQueue() + { + semaphore.Release(); + } + + public int SpotsLeft + { + get => semaphore.CurrentCount; + } + } +} diff --git a/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj b/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj new file mode 100644 index 0000000000..b1559a97fe --- /dev/null +++ b/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj @@ -0,0 +1,10 @@ + + + + netcoreapp3.0 + + + + + + diff --git a/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs new file mode 100644 index 0000000000..33bf6ea861 --- /dev/null +++ b/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs @@ -0,0 +1,56 @@ +// 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 Xunit; +using System.Threading; +using System.Threading.Tasks; +using System; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Testing; + +namespace Microsoft.AspNetCore.RequestQueue.Tests +{ + public class SemaphoreWrapperTests + { + [Fact] + public async Task TestBehavior() + { + var s = new SemaphoreWrapper(1); + Assert.Equal(1, s.SpotsLeft); + + await s.EnterQueue(); + Assert.Equal(0, s.SpotsLeft); + + s.LeaveQueue(); + Assert.Equal(1, s.SpotsLeft); + } + + [Fact] + public void TestQueueLength() + { + var s = new SemaphoreWrapper(2); + + var t1 = s.EnterQueue(); + Assert.True(t1.IsCompleted); + + var t2 = s.EnterQueue(); + Assert.True(t2.IsCompleted); + + var t3 = s.EnterQueue(); + Assert.False(t3.IsCompleted); + } + + [Fact] + public async Task TestWaiting() + { + var s = new SemaphoreWrapper(1); + await s.EnterQueue(); + + var waitingTask = s.EnterQueue(); + Assert.False(waitingTask.IsCompleted); + + s.LeaveQueue(); + await waitingTask.TimeoutAfter(TimeSpan.FromSeconds(1)); + } + } +} diff --git a/src/Middleware/build.cmd b/src/Middleware/build.cmd new file mode 100644 index 0000000000..033fe6f614 --- /dev/null +++ b/src/Middleware/build.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SET RepoRoot=%~dp0..\.. +%RepoRoot%\build.cmd -projects %~dp0\**\*.*proj %* From 61f028ad322d5e0e84e2f0957090e3b856d68616 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Thu, 16 May 2019 16:34:37 -0700 Subject: [PATCH 03/41] Renamed project, cleaned sln file --- eng/ProjectReferences.props | 2 +- src/Middleware/Middleware.sln | 47 +++++++++++++++++++ .../Microsoft.AspNetCore.RequestQueue.csproj | 0 ...t.AspNetCore.RequestQueue.netcoreapp3.0.cs | 0 .../sample/RequestThrottlingSample.csproj} | 0 .../sample/Startup.cs | 0 ...osoft.AspNetCore.RequestThrottling.csproj} | 2 +- .../src/Properties/AssemblyInfo.cs | 6 +++ .../src/SemaphoreWrapper.cs | 0 ...AspNetCore.RequestThrottling.Tests.csproj} | 2 +- .../test/SemaphoreWrapperTests.cs | 0 11 files changed, 56 insertions(+), 3 deletions(-) rename src/Middleware/{RequestQueue => RequestThrottling}/ref/Microsoft.AspNetCore.RequestQueue.csproj (100%) rename src/Middleware/{RequestQueue => RequestThrottling}/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs (100%) rename src/Middleware/{RequestQueue/sample/RequestQueueSample.csproj => RequestThrottling/sample/RequestThrottlingSample.csproj} (100%) rename src/Middleware/{RequestQueue => RequestThrottling}/sample/Startup.cs (100%) rename src/Middleware/{RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj => RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj} (95%) create mode 100644 src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs rename src/Middleware/{RequestQueue => RequestThrottling}/src/SemaphoreWrapper.cs (100%) rename src/Middleware/{RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj => RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj} (71%) rename src/Middleware/{RequestQueue => RequestThrottling}/test/SemaphoreWrapperTests.cs (100%) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index e274d05a71..97f1875b9e 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -79,7 +79,7 @@ - + diff --git a/src/Middleware/Middleware.sln b/src/Middleware/Middleware.sln index 5f16259900..a02c8c40a8 100644 --- a/src/Middleware/Middleware.sln +++ b/src/Middleware/Middleware.sln @@ -283,6 +283,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HeaderPropagationSample", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "..\Servers\IIS\IIS\src\Microsoft.AspNetCore.Server.IIS.csproj", "{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RequestThrottling", "RequestThrottling", "{8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RequestThrottlingSample", "RequestThrottling\sample\RequestThrottlingSample.csproj", "{6720919C-0DEA-49E1-90DC-F1883F7919CD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RequestThrottling", "RequestThrottling\src\Microsoft.AspNetCore.RequestThrottling.csproj", "{4CE2384D-6B88-4824-ADD1-4183D180FEFF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RequestThrottling.Tests", "RequestThrottling\test\Microsoft.AspNetCore.RequestThrottling.Tests.csproj", "{353AA2B0-1013-486C-B5BD-9379385CA403}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1541,6 +1549,42 @@ Global {B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x64.Build.0 = Release|Any CPU {B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x86.ActiveCfg = Release|Any CPU {B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x86.Build.0 = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x64.Build.0 = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x86.Build.0 = Debug|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|Any CPU.Build.0 = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x64.ActiveCfg = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x64.Build.0 = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x86.ActiveCfg = Release|Any CPU + {6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x86.Build.0 = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x64.ActiveCfg = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x64.Build.0 = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x86.ActiveCfg = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x86.Build.0 = Debug|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|Any CPU.Build.0 = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x64.ActiveCfg = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x64.Build.0 = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x86.ActiveCfg = Release|Any CPU + {4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x86.Build.0 = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x64.ActiveCfg = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x64.Build.0 = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x86.ActiveCfg = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x86.Build.0 = Debug|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|Any CPU.ActiveCfg = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|Any CPU.Build.0 = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x64.ActiveCfg = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x64.Build.0 = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x86.ActiveCfg = Release|Any CPU + {353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1663,6 +1707,9 @@ Global {179A159B-87EA-4353-BE92-4FB6CC05BC7D} = {0437D207-864E-429C-92B4-9D08D290188C} {CDE2E736-A034-4748-98C4-0DEDAAC8063D} = {179A159B-87EA-4353-BE92-4FB6CC05BC7D} {B9BE1823-B555-4AAB-AEBC-C8C3F48C8861} = {ACA6DDB9-7592-47CE-A740-D15BF307E9E0} + {6720919C-0DEA-49E1-90DC-F1883F7919CD} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343} + {4CE2384D-6B88-4824-ADD1-4183D180FEFF} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343} + {353AA2B0-1013-486C-B5BD-9379385CA403} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA} diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj similarity index 100% rename from src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj rename to src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs similarity index 100% rename from src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs rename to src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs diff --git a/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj similarity index 100% rename from src/Middleware/RequestQueue/sample/RequestQueueSample.csproj rename to src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj diff --git a/src/Middleware/RequestQueue/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs similarity index 100% rename from src/Middleware/RequestQueue/sample/Startup.cs rename to src/Middleware/RequestThrottling/sample/Startup.cs diff --git a/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj similarity index 95% rename from src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj rename to src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj index ddc77f3682..9f410e75c5 100644 --- a/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj +++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. diff --git a/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs b/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1dcaedfaa6 --- /dev/null +++ b/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.RequestThrottling.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs similarity index 100% rename from src/Middleware/RequestQueue/src/SemaphoreWrapper.cs rename to src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs diff --git a/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj similarity index 71% rename from src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj rename to src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj index b1559a97fe..8c0dd8e989 100644 --- a/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj +++ b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj @@ -5,6 +5,6 @@ - + diff --git a/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs similarity index 100% rename from src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs rename to src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs From ac305849e35e70f195d3e7164ec04ddf4e2f32a1 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Thu, 16 May 2019 16:55:46 -0700 Subject: [PATCH 04/41] cleanup from PR feedback --- .../RequestThrottling/sample/Startup.cs | 4 +-- ...rosoft.AspNetCore.RequestThrottling.csproj | 5 ++-- .../RequestThrottling/src/SemaphoreWrapper.cs | 16 ++++++------ .../test/SemaphoreWrapperTests.cs | 26 +++++++++++++------ 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs index 5768d045e6..a9bc74e319 100644 --- a/src/Middleware/RequestThrottling/sample/Startup.cs +++ b/src/Middleware/RequestThrottling/sample/Startup.cs @@ -12,8 +12,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace RequestQueueSample -{ +namespace RequestThrottlingSample +{ public class Startup { // This method gets called by the runtime. Use this method to add services to the container. diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj index 9f410e75c5..c9b165fa16 100644 --- a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj +++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj @@ -1,11 +1,10 @@ - + ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. netcoreapp3.0 - $(NoWarn);CS1591 - true true + aspnetcore;queue;queuing $(WarningsNotAsErrors);CS1591 diff --git a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs index aff2f66919..7bfb6d377a 100644 --- a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs +++ b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs @@ -4,30 +4,30 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNetCore.RequestQueue +namespace Microsoft.AspNetCore.RequestThrottling { internal class SemaphoreWrapper { - private static SemaphoreSlim semaphore; + private SemaphoreSlim _semaphore; - public SemaphoreWrapper(int qlength) + public SemaphoreWrapper(int queueLength) { - semaphore = new SemaphoreSlim(qlength); + _semaphore = new SemaphoreSlim(queueLength); } public Task EnterQueue() { - return semaphore.WaitAsync(); + return _semaphore.WaitAsync(); } public void LeaveQueue() { - semaphore.Release(); + _semaphore.Release(); } - public int SpotsLeft + public int Count { - get => semaphore.CurrentCount; + get => _semaphore.CurrentCount; } } } diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs index 33bf6ea861..d6c990bf1f 100644 --- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs +++ b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs @@ -8,25 +8,25 @@ using System; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Testing; -namespace Microsoft.AspNetCore.RequestQueue.Tests +namespace Microsoft.AspNetCore.RequestThrottling.Tests { public class SemaphoreWrapperTests { [Fact] - public async Task TestBehavior() + public async Task TracksQueueLength() { var s = new SemaphoreWrapper(1); - Assert.Equal(1, s.SpotsLeft); + Assert.Equal(1, s.Count); await s.EnterQueue(); - Assert.Equal(0, s.SpotsLeft); + Assert.Equal(0, s.Count); s.LeaveQueue(); - Assert.Equal(1, s.SpotsLeft); + Assert.Equal(1, s.Count); } [Fact] - public void TestQueueLength() + public void DoesNotWaitIfSpaceAvailible() { var s = new SemaphoreWrapper(2); @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.RequestQueue.Tests } [Fact] - public async Task TestWaiting() + public async Task WaitsIfNoSpaceAvailible() { var s = new SemaphoreWrapper(1); await s.EnterQueue(); @@ -50,7 +50,17 @@ namespace Microsoft.AspNetCore.RequestQueue.Tests Assert.False(waitingTask.IsCompleted); s.LeaveQueue(); - await waitingTask.TimeoutAfter(TimeSpan.FromSeconds(1)); + await waitingTask.TimeoutAfter(TimeSpan.FromSeconds(30)); + } + + [Fact] + public async Task IsEncapsulated() + { + var s1 = new SemaphoreWrapper(1); + var s2 = new SemaphoreWrapper(1); + + await s1.EnterQueue(); + await s2.EnterQueue().TimeoutAfter(TimeSpan.FromSeconds(30)); } } } From 04ce6ef4c80bd9749d3622cfd9b037c4aedb6bd6 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Thu, 16 May 2019 16:59:40 -0700 Subject: [PATCH 05/41] IDisposable fix --- .../RequestThrottling/src/SemaphoreWrapper.cs | 7 ++++++- .../RequestThrottling/test/SemaphoreWrapperTests.cs | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs index 7bfb6d377a..4c79b94777 100644 --- a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs +++ b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.AspNetCore.RequestThrottling { - internal class SemaphoreWrapper + internal class SemaphoreWrapper : IDisposable { private SemaphoreSlim _semaphore; @@ -29,5 +29,10 @@ namespace Microsoft.AspNetCore.RequestThrottling { get => _semaphore.CurrentCount; } + + public void Dispose() + { + _semaphore.Dispose(); + } } } diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs index d6c990bf1f..368804c8cd 100644 --- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs +++ b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests [Fact] public async Task TracksQueueLength() { - var s = new SemaphoreWrapper(1); + using var s = new SemaphoreWrapper(1); Assert.Equal(1, s.Count); await s.EnterQueue(); @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests [Fact] public void DoesNotWaitIfSpaceAvailible() { - var s = new SemaphoreWrapper(2); + using var s = new SemaphoreWrapper(2); var t1 = s.EnterQueue(); Assert.True(t1.IsCompleted); @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests [Fact] public async Task WaitsIfNoSpaceAvailible() { - var s = new SemaphoreWrapper(1); + using var s = new SemaphoreWrapper(1); await s.EnterQueue(); var waitingTask = s.EnterQueue(); @@ -56,8 +56,8 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests [Fact] public async Task IsEncapsulated() { - var s1 = new SemaphoreWrapper(1); - var s2 = new SemaphoreWrapper(1); + using var s1 = new SemaphoreWrapper(1); + using var s2 = new SemaphoreWrapper(1); await s1.EnterQueue(); await s2.EnterQueue().TimeoutAfter(TimeSpan.FromSeconds(30)); From 6c5274a09fa80992b90844e0d32e2fe4d12265f2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 16 May 2019 17:39:03 -0700 Subject: [PATCH 06/41] Support a few more complex types with DefaultTempDataSerializer Fixes https://github.com/aspnet/AspNetCore/issues/9540 --- .../Identity.DefaultUI.WebSite/StartupBase.cs | 3 +- .../test/BsonTempDataSerializerTest.cs | 28 +++++ .../DefaultTempDataSerializer.cs | 87 +++++++++++++- .../DefaultTempDataSerializerTest.cs | 21 ++++ .../TempDataSerializerTestBase.cs | 113 ++++++++++++++++++ src/Mvc/MvcNoDeps.slnf | 73 +++++++++++ 6 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 src/Mvc/MvcNoDeps.slnf diff --git a/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs b/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs index 14ae5b2db4..f3915ed44e 100644 --- a/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs +++ b/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs @@ -50,8 +50,7 @@ namespace Identity.DefaultUI.WebSite .AddRoles() .AddEntityFrameworkStores(); - services.AddMvc() - .AddNewtonsoftJson(); + services.AddMvc(); services.AddSingleton(); } diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs index a2438b1b8b..bae4f65fd7 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs @@ -235,6 +235,34 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson Assert.Equal(value.ToString(), roundTripValue); } + [Fact] + public void RoundTripTest_ListOfDateTime() + { + // Documents the behavior of round-tripping a Guid value as a string + // Arrange + var key = "test-key"; + var dateTime = new DateTime(2007, 1, 1); + var testProvider = GetTempDataSerializer(); + var value = new List + { + dateTime, + dateTime.AddDays(3), + }; + + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + private class TestItem { public int DummyInt { get; set; } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs index 27b30c9dc6..a67670e595 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs @@ -63,6 +63,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure deserializedValue = null; break; + case JsonValueType.Array: + deserializedValue = DeserializeArray(item.Value); + break; + + case JsonValueType.Object: + deserializedValue = DeserializeDictionaryEntry(item.Value); + break; + default: throw new InvalidOperationException(Resources.FormatTempData_CannotDeserializeType(item.Value.Type)); } @@ -73,6 +81,53 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure return deserialized; } + private static object DeserializeArray(in JsonElement arrayElement) + { + if (arrayElement.GetArrayLength() == 0) + { + // We have to infer the type of the array by inspecting it's elements. + // If there's nothing to inspect, return a null value since we do not know + // what type the user code is expecting. + return null; + } + + if (arrayElement[0].Type == JsonValueType.String) + { + var array = new List(); + + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetString()); + } + + return array.ToArray(); + } + else if (arrayElement[0].Type == JsonValueType.Number) + { + var array = new List(); + + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetInt32()); + } + + return array.ToArray(); + } + + throw new InvalidOperationException(Resources.FormatTempData_CannotDeserializeType(arrayElement.Type)); + } + + private static object DeserializeDictionaryEntry(in JsonElement objectElement) + { + var dictionary = new Dictionary(StringComparer.Ordinal); + foreach (var item in objectElement.EnumerateObject()) + { + dictionary[item.Name] = item.Value.GetString(); + } + + return dictionary; + } + public override byte[] Serialize(IDictionary values) { if (values == null || values.Count == 0) @@ -126,6 +181,33 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure case Guid guid: writer.WriteString(key, guid); break; + + case ICollection intCollection: + writer.WriteStartArray(key); + foreach (var element in intCollection) + { + writer.WriteNumberValue(element); + } + writer.WriteEndArray(); + break; + + case ICollection stringCollection: + writer.WriteStartArray(key); + foreach (var element in stringCollection) + { + writer.WriteStringValue(element); + } + writer.WriteEndArray(); + break; + + case IDictionary dictionary: + writer.WriteStartObject(key); + foreach (var element in dictionary) + { + writer.WriteString(element.Key, element.Value); + } + writer.WriteEndObject(); + break; } } writer.WriteEndObject(); @@ -150,7 +232,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure type == typeof(string) || type == typeof(bool) || type == typeof(DateTime) || - type == typeof(Guid); + type == typeof(Guid) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(IDictionary).IsAssignableFrom(type); } } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs index 982e9fc9f1..22f2185fd1 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs @@ -34,5 +34,26 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure var roundTripValue = Assert.IsType(values[key]); Assert.Equal(value.ToString("r"), roundTripValue); } + + [Fact] + public override void RoundTripTest_DictionaryOfInt() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", 7 }, + { "Key2", 24 }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var ex = Assert.Throws(() => testProvider.Serialize(input)); + Assert.Equal($"The '{testProvider.GetType()}' cannot serialize an object of type '{value.GetType()}'.", ex.Message); + } } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs index 38a540f8e7..93fb4df0e2 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs @@ -239,6 +239,119 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure Assert.Equal(value, roundTripValue); } + [Fact] + public void RoundTripTest_CollectionOfInts() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new[] { 1, 2, 4, 3 }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_ArrayOfStringss() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new[] { "Hello", "world" }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_ListOfStringss() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new List { "Hello", "world" }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_DictionaryOfString() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", "Value1" }, + { "Key2", "Value2" }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType>(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public virtual void RoundTripTest_DictionaryOfInt() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", 7 }, + { "Key2", 24 }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType>(values[key]); + Assert.Equal(value, roundTripValue); + } + protected abstract TempDataSerializer GetTempDataSerializer(); } } diff --git a/src/Mvc/MvcNoDeps.slnf b/src/Mvc/MvcNoDeps.slnf new file mode 100644 index 0000000000..f67bdb6d53 --- /dev/null +++ b/src/Mvc/MvcNoDeps.slnf @@ -0,0 +1,73 @@ +{ + "solution": { + "path": "Mvc.sln", + "projects": [ + "test\\WebSites\\BasicWebSite\\BasicWebSite.csproj", + "test\\WebSites\\RoutingWebSite\\Mvc.RoutingWebSite.csproj", + "test\\WebSites\\RazorWebSite\\RazorWebSite.csproj", + "test\\WebSites\\FormatterWebSite\\FormatterWebSite.csproj", + "test\\WebSites\\ApiExplorerWebSite\\ApiExplorerWebSite.csproj", + "test\\WebSites\\VersioningWebSite\\VersioningWebSite.csproj", + "test\\WebSites\\TagHelpersWebSite\\TagHelpersWebSite.csproj", + "test\\WebSites\\FilesWebSite\\FilesWebSite.csproj", + "test\\WebSites\\ApplicationModelWebSite\\ApplicationModelWebSite.csproj", + "test\\WebSites\\HtmlGenerationWebSite\\HtmlGenerationWebSite.csproj", + "test\\WebSites\\ErrorPageMiddlewareWebSite\\ErrorPageMiddlewareWebSite.csproj", + "test\\WebSites\\XmlFormattersWebSite\\XmlFormattersWebSite.csproj", + "test\\WebSites\\ControllersFromServicesWebSite\\ControllersFromServicesWebSite.csproj", + "test\\WebSites\\ControllersFromServicesClassLibrary\\ControllersFromServicesClassLibrary.csproj", + "test\\WebSites\\CorsWebSite\\CorsWebSite.csproj", + "samples\\MvcSandbox\\MvcSandbox.csproj", + "test\\WebSites\\SimpleWebSite\\SimpleWebSite.csproj", + "test\\WebSites\\SecurityWebSite\\SecurityWebSite.csproj", + "test\\WebSites\\RazorPagesWebSite\\RazorPagesWebSite.csproj", + "benchmarks\\Microsoft.AspNetCore.Mvc.Performance\\Microsoft.AspNetCore.Mvc.Performance.csproj", + "test\\WebSites\\RazorBuildWebSite\\RazorBuildWebSite.csproj", + "test\\WebSites\\RazorBuildWebSite.Views\\RazorBuildWebSite.Views.csproj", + "Mvc.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Analyzers.csproj", + "Mvc.Analyzers\\test\\Mvc.Analyzers.Test.csproj", + "test\\WebSites\\RazorPagesClassLibrary\\RazorPagesClassLibrary.csproj", + "shared\\Mvc.Views.TestCommon\\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", + "Mvc.Api.Analyzers\\test\\Mvc.Api.Analyzers.Test.csproj", + "Mvc.Api.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", + "test\\WebSites\\GenericHostWebSite\\GenericHostWebSite.csproj", + "Mvc\\src\\Microsoft.AspNetCore.Mvc.csproj", + "Mvc\\test\\Microsoft.AspNetCore.Mvc.Test.csproj", + "Mvc.Abstractions\\src\\Microsoft.AspNetCore.Mvc.Abstractions.csproj", + "Mvc.Abstractions\\test\\Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj", + "Mvc.ApiExplorer\\src\\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj", + "Mvc.ApiExplorer\\test\\Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj", + "Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj", + "Mvc.Core\\test\\Microsoft.AspNetCore.Mvc.Core.Test.csproj", + "Mvc.Cors\\src\\Microsoft.AspNetCore.Mvc.Cors.csproj", + "Mvc.Cors\\test\\Microsoft.AspNetCore.Mvc.Cors.Test.csproj", + "Mvc.DataAnnotations\\src\\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj", + "Mvc.DataAnnotations\\test\\Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj", + "Mvc.Formatters.Json\\src\\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj", + "Mvc.Formatters.Xml\\src\\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj", + "Mvc.Formatters.Xml\\test\\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj", + "Mvc.Localization\\src\\Microsoft.AspNetCore.Mvc.Localization.csproj", + "Mvc.Localization\\test\\Microsoft.AspNetCore.Mvc.Localization.Test.csproj", + "Mvc.Razor\\src\\Microsoft.AspNetCore.Mvc.Razor.csproj", + "Mvc.Razor\\test\\Microsoft.AspNetCore.Mvc.Razor.Test.csproj", + "Mvc.RazorPages\\src\\Microsoft.AspNetCore.Mvc.RazorPages.csproj", + "Mvc.RazorPages\\test\\Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj", + "Mvc.TagHelpers\\src\\Microsoft.AspNetCore.Mvc.TagHelpers.csproj", + "Mvc.TagHelpers\\test\\Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj", + "Mvc.ViewFeatures\\src\\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj", + "Mvc.ViewFeatures\\test\\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj", + "test\\Mvc.FunctionalTests\\Microsoft.AspNetCore.Mvc.FunctionalTests.csproj", + "test\\Mvc.IntegrationTests\\Microsoft.AspNetCore.Mvc.IntegrationTests.csproj", + "shared\\Mvc.TestDiagnosticListener\\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj", + "Mvc.Testing\\src\\Microsoft.AspNetCore.Mvc.Testing.csproj", + "shared\\Mvc.Core.TestCommon\\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", + "Mvc.NewtonsoftJson\\src\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj", + "Mvc.NewtonsoftJson\\test\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj", + "Mvc.Razor.RuntimeCompilation\\src\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj", + "Mvc.Razor.RuntimeCompilation\\test\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.Test.csproj", + "test\\WebSites\\RazorBuildWebSite.PrecompiledViews\\RazorBuildWebSite.PrecompiledViews.csproj", + "Mvc.Components.Prerendering\\src\\Microsoft.AspNetCore.Mvc.Components.Prerendering.csproj", + "Mvc.Components.Prerendering\\test\\Microsoft.AspNetCore.Mvc.Components.Prerendering.Test.csproj", + ] + } +} From 9363eff2a8261c511923c74065bbbfc7e12c2278 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Fri, 17 May 2019 10:40:19 -0700 Subject: [PATCH 07/41] OrTimeout Extension --- .../RequestThrottling/sample/Startup.cs | 2 ++ .../test/SemaphoreWrapperTests.cs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs index a9bc74e319..ea4969c9bd 100644 --- a/src/Middleware/RequestThrottling/sample/Startup.cs +++ b/src/Middleware/RequestThrottling/sample/Startup.cs @@ -26,7 +26,9 @@ namespace RequestThrottlingSample { app.Run(async context => { + Console.WriteLine("HEWWWO?"); await context.Response.WriteAsync("Hello world!"); + Console.WriteLine("GOODBWWYWE!"); }); } diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs index 368804c8cd..f9ddd80420 100644 --- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs +++ b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs @@ -10,6 +10,13 @@ using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.RequestThrottling.Tests { + public static class TaskExtensions + { + public static Task OrTimeout(this Task task, int seconds = 30) + { + return task.TimeoutAfter(TimeSpan.FromSeconds(seconds)); + } + } public class SemaphoreWrapperTests { [Fact] @@ -18,7 +25,7 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests using var s = new SemaphoreWrapper(1); Assert.Equal(1, s.Count); - await s.EnterQueue(); + await s.EnterQueue().OrTimeout(); Assert.Equal(0, s.Count); s.LeaveQueue(); @@ -44,13 +51,13 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests public async Task WaitsIfNoSpaceAvailible() { using var s = new SemaphoreWrapper(1); - await s.EnterQueue(); + await s.EnterQueue().OrTimeout(); var waitingTask = s.EnterQueue(); Assert.False(waitingTask.IsCompleted); s.LeaveQueue(); - await waitingTask.TimeoutAfter(TimeSpan.FromSeconds(30)); + await waitingTask.OrTimeout(); } [Fact] @@ -59,8 +66,8 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests using var s1 = new SemaphoreWrapper(1); using var s2 = new SemaphoreWrapper(1); - await s1.EnterQueue(); - await s2.EnterQueue().TimeoutAfter(TimeSpan.FromSeconds(30)); + await s1.EnterQueue().OrTimeout(); + await s2.EnterQueue().OrTimeout(); } } } From 3e6f70d5e8527fd59b27d137ca0d66564f485945 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Fri, 17 May 2019 10:54:56 -0700 Subject: [PATCH 08/41] Justin fixed refs --- .../Microsoft.AspNetCore.RequestQueue.csproj | 14 ----- ...t.AspNetCore.RequestQueue.netcoreapp3.0.cs | 51 ------------------- ...rosoft.AspNetCore.RequestThrottling.csproj | 10 ++++ ...NetCore.RequestThrottling.netcoreapp3.0.cs | 3 ++ ...rosoft.AspNetCore.RequestThrottling.csproj | 7 --- 5 files changed, 13 insertions(+), 72 deletions(-) delete mode 100644 src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj delete mode 100644 src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs create mode 100644 src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj create mode 100644 src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj deleted file mode 100644 index 05f6a42637..0000000000 --- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - netcoreapp3.0 - - - - - - - - - - diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs deleted file mode 100644 index dc22bca076..0000000000 --- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs +++ /dev/null @@ -1,51 +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. - -namespace Microsoft.AspNetCore.Builder -{ - public static partial class HstsBuilderExtensions - { - public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHsts(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } - } - public static partial class HstsServicesExtensions - { - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHsts(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } - } - public static partial class HttpsPolicyBuilderExtensions - { - public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHttpsRedirection(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } - } - public static partial class HttpsRedirectionServicesExtensions - { - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpsRedirection(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } - } -} -namespace Microsoft.AspNetCore.HttpsPolicy -{ - public partial class HstsMiddleware - { - public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options) { } - public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } - } - public partial class HstsOptions - { - public HstsOptions() { } - public System.Collections.Generic.IList ExcludedHosts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool IncludeSubDomains { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public System.TimeSpan MaxAge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public bool Preload { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } - public partial class HttpsRedirectionMiddleware - { - public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature serverAddressesFeature) { } - public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } - } - public partial class HttpsRedirectionOptions - { - public HttpsRedirectionOptions() { } - public int? HttpsPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - public int RedirectStatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - } -} diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj new file mode 100644 index 0000000000..0a1bcdd0b9 --- /dev/null +++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj @@ -0,0 +1,10 @@ + + + + netcoreapp3.0 + + + + + + diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs new file mode 100644 index 0000000000..618082bc4a --- /dev/null +++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs @@ -0,0 +1,3 @@ +// 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. + diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj index c9b165fa16..3abc5a62f4 100644 --- a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj +++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj @@ -8,11 +8,4 @@ $(WarningsNotAsErrors);CS1591 - - - - - - - From 5e85b3773b4c46a24e27fa71d13fd431d9056e01 Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Fri, 17 May 2019 14:27:06 -0700 Subject: [PATCH 09/41] ready for final review --- eng/ProjectReferences.props | 2 +- .../RequestThrottling/RequestThrottling.slnf | 25 +++++++ .../RequestThrottling/sample/Startup.cs | 4 +- .../test/SemaphoreWrapperTests.cs | 7 -- .../RequestThrottling/test/TaskExtensions.cs | 69 +++++++++++++++++++ 5 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 src/Middleware/RequestThrottling/RequestThrottling.slnf create mode 100644 src/Middleware/RequestThrottling/test/TaskExtensions.cs diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 97f1875b9e..920e15a65e 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -79,10 +79,10 @@ - + diff --git a/src/Middleware/RequestThrottling/RequestThrottling.slnf b/src/Middleware/RequestThrottling/RequestThrottling.slnf new file mode 100644 index 0000000000..d434fbc862 --- /dev/null +++ b/src/Middleware/RequestThrottling/RequestThrottling.slnf @@ -0,0 +1,25 @@ +{ + "solution": { + "path": "..\\Middleware.sln", + "projects": [ + "..\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", + "..\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", + "..\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", + "..\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", + "..\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", + "..\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", + "..\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", + "..\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", + "..\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", + "..\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", + "..\\Servers\\Kestrel\\Transport.Abstractions\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj", + "..\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", + "..\\http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", + "..\\http\\http\\src\\Microsoft.AspNetCore.Http.csproj", + "HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj", + "RequestThrottling\\sample\\RequestThrottlingSample.csproj", + "RequestThrottling\\src\\Microsoft.AspNetCore.RequestThrottling.csproj", + "RequestThrottling\\test\\Microsoft.AspNetCore.RequestThrottling.Tests.csproj" + ] + } +} \ No newline at end of file diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs index ea4969c9bd..8f0ba344c6 100644 --- a/src/Middleware/RequestThrottling/sample/Startup.cs +++ b/src/Middleware/RequestThrottling/sample/Startup.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace RequestThrottlingSample -{ +{ public class Startup { // This method gets called by the runtime. Use this method to add services to the container. @@ -26,9 +26,7 @@ namespace RequestThrottlingSample { app.Run(async context => { - Console.WriteLine("HEWWWO?"); await context.Response.WriteAsync("Hello world!"); - Console.WriteLine("GOODBWWYWE!"); }); } diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs index f9ddd80420..b5cdfce18f 100644 --- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs +++ b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs @@ -10,13 +10,6 @@ using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.RequestThrottling.Tests { - public static class TaskExtensions - { - public static Task OrTimeout(this Task task, int seconds = 30) - { - return task.TimeoutAfter(TimeSpan.FromSeconds(seconds)); - } - } public class SemaphoreWrapperTests { [Fact] diff --git a/src/Middleware/RequestThrottling/test/TaskExtensions.cs b/src/Middleware/RequestThrottling/test/TaskExtensions.cs new file mode 100644 index 0000000000..52ec0c4303 --- /dev/null +++ b/src/Middleware/RequestThrottling/test/TaskExtensions.cs @@ -0,0 +1,69 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Testing; + +namespace System.Threading.Tasks +{ +#if TESTUTILS + public +#else + internal +#endif + static class TaskExtensions + { + private const int DefaultTimeout = 30 * 1000; + + public static Task OrTimeout(this Task task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber); + } + + public static Task OrTimeout(this Task task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0); + } + + public static Task OrTimeout(this ValueTask task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) => + OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber); + + public static Task OrTimeout(this ValueTask task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) => + task.AsTask().OrTimeout(timeout, memberName, filePath, lineNumber); + + public static Task OrTimeout(this Task task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber); + } + + public static Task OrTimeout(this Task task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) + { + return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0); + } + + public static async Task OrThrowIfOtherFails(this Task task, Task otherTask) + { + var completed = await Task.WhenAny(task, otherTask); + if (completed == otherTask && otherTask.IsFaulted) + { + // Manifest the exception + otherTask.GetAwaiter().GetResult(); + throw new Exception("Unreachable code"); + } + else + { + // Await the task we were asked to await. Either it's finished, or the otherTask finished successfully, and it's not our job to check that + await task; + } + } + + public static async Task OrThrowIfOtherFails(this Task task, Task otherTask) + { + await OrThrowIfOtherFails((Task)task, otherTask); + + // If we get here, 'task' is finished and succeeded. + return task.GetAwaiter().GetResult(); + } + } +} From 206c1e8a8a019119bf8a0d418427659ab0a8cfab Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Fri, 17 May 2019 14:37:16 -0700 Subject: [PATCH 10/41] Removed error line --- .../src/Microsoft.AspNetCore.RequestThrottling.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj index 3abc5a62f4..5014e9cec5 100644 --- a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj +++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj @@ -1,11 +1,10 @@ - + ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. netcoreapp3.0 true aspnetcore;queue;queuing - $(WarningsNotAsErrors);CS1591 From fdc056cebe69a1883b28d98d3a23064142364128 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 17 May 2019 15:12:24 -0700 Subject: [PATCH 11/41] Fixup --- src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs index bae4f65fd7..5bfe813d19 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs @@ -238,7 +238,6 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson [Fact] public void RoundTripTest_ListOfDateTime() { - // Documents the behavior of round-tripping a Guid value as a string // Arrange var key = "test-key"; var dateTime = new DateTime(2007, 1, 1); From 68606a2d804a1873dcfc2eccf4c1d9ccaf1c67dd Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Fri, 17 May 2019 16:56:17 -0700 Subject: [PATCH 12/41] removed redundant HttpsPolicy dependency --- .../RequestThrottling/sample/RequestThrottlingSample.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj index 57892c2249..fd9214e35a 100644 --- a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj +++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj @@ -7,7 +7,7 @@ - + From 7ff52931770c3fe1fa4f00b3a2db31a8d462085d Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray Date: Sat, 18 May 2019 18:31:31 -0700 Subject: [PATCH 13/41] added HttpsPolicy back --- .../RequestThrottling/sample/RequestThrottlingSample.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj index fd9214e35a..aba86dfadf 100644 --- a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj +++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -7,6 +7,7 @@ + From 147880f79656385cc94752e46d05ba2265eaea48 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 May 2019 15:41:02 +0100 Subject: [PATCH 14/41] Components auth step 2 (#10293) * CR feedback left over from #10227 * Begin adding E2E test case * Add cookie auth and test login page * Make E2E auth component work client-side too * Restructure auth E2E tests around a router so there can easily be multiple such test components * Add E2E test case for AuthorizeView * Prepare for E2E test implementations * Fix ToBaseRelativePath handling of hashes ... otherwise E2E test will fail, because we're using the hash to control server-or-client execution * Decouple E2E execution mode from hosting mode * Actual E2E tests for cascading authentication state * Actual E2E tests for AuthorizeView (in "no authentication rule" mode) * Fix inconsistent namespace * CR: Manual ref assembly definitions for AuthorizeView/CascadingAuthenticationState --- eng/GenAPI.exclusions.txt | 2 + .../Blazor/test/WebAssemblyUriHelperTest.cs | 3 + ...etCore.Components.netstandard2.0.Manual.cs | 26 ++++++ ...ft.AspNetCore.Components.netstandard2.0.cs | 24 ----- .../src/Auth/AuthenticationState.cs | 10 +- .../src/Auth/AuthenticationStateProvider.cs | 7 +- .../Components/src/UriHelperBase.cs | 7 +- .../Infrastructure/BasicTestAppTestBase.cs | 2 +- .../ToggleExecutionModeServerFixture.cs | 6 +- .../ServerExecutionTests/PrerenderingTest.cs | 2 +- .../ServerExecutionTestExtensions.cs | 1 + .../ServerExecutionTests/TestSubclasses.cs | 8 ++ src/Components/test/E2ETest/Tests/AuthTest.cs | 92 +++++++++++++++++++ src/Components/test/E2ETest/Tests/BindTest.cs | 2 +- .../test/E2ETest/Tests/CascadingValueTest.cs | 2 +- .../E2ETest/Tests/ComponentRenderingTest.cs | 2 +- .../test/E2ETest/Tests/EventBubblingTest.cs | 2 +- .../test/E2ETest/Tests/EventCallbackTest.cs | 2 +- .../test/E2ETest/Tests/FormsTest.cs | 2 +- .../test/E2ETest/Tests/InteropTest.cs | 2 +- src/Components/test/E2ETest/Tests/KeyTest.cs | 2 +- .../BasicTestApp/AuthTest/AuthHome.razor | 3 + .../BasicTestApp/AuthTest/AuthRouter.razor | 30 ++++++ .../AuthTest/AuthorizeViewCases.razor | 17 ++++ ...CascadingAuthenticationStateConsumer.razor | 47 ++++++++++ .../ClientSideAuthenticationStateData.cs | 17 ++++ .../BasicTestApp/AuthTest/Links.razor | 8 ++ .../ServerAuthenticationStateProvider.cs | 43 +++++++++ .../test/testassets/BasicTestApp/Index.razor | 2 + .../test/testassets/BasicTestApp/Startup.cs | 3 + .../TestServer/Components.TestServer.csproj | 3 +- .../TestServer/Controllers/UserController.cs | 28 ++++++ .../TestServer/Pages/Authentication.cshtml | 72 +++++++++++++++ .../test/testassets/TestServer/Startup.cs | 4 + 34 files changed, 434 insertions(+), 49 deletions(-) create mode 100644 src/Components/test/E2ETest/Tests/AuthTest.cs create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/AuthHome.razor create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/AuthorizeViewCases.razor create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/CascadingAuthenticationStateConsumer.razor create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/ClientSideAuthenticationStateData.cs create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/Links.razor create mode 100644 src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs create mode 100644 src/Components/test/testassets/TestServer/Controllers/UserController.cs create mode 100644 src/Components/test/testassets/TestServer/Pages/Authentication.cshtml diff --git a/eng/GenAPI.exclusions.txt b/eng/GenAPI.exclusions.txt index 61d8c412b2..4595fc772a 100644 --- a/eng/GenAPI.exclusions.txt +++ b/eng/GenAPI.exclusions.txt @@ -4,6 +4,8 @@ T:Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame T:Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel T:Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel # Manually implemented - https://github.com/aspnet/AspNetCore/issues/8825 +T:Microsoft.AspNetCore.Components.AuthorizeView +T:Microsoft.AspNetCore.Components.CascadingAuthenticationState T:Microsoft.AspNetCore.Components.CascadingValue`1 T:Microsoft.AspNetCore.Components.Forms.DataAnnotationsValidator T:Microsoft.AspNetCore.Components.Forms.EditForm diff --git a/src/Components/Blazor/Blazor/test/WebAssemblyUriHelperTest.cs b/src/Components/Blazor/Blazor/test/WebAssemblyUriHelperTest.cs index 45c94a71d2..2903d7fcd3 100644 --- a/src/Components/Blazor/Blazor/test/WebAssemblyUriHelperTest.cs +++ b/src/Components/Blazor/Blazor/test/WebAssemblyUriHelperTest.cs @@ -29,6 +29,9 @@ namespace Microsoft.AspNetCore.Blazor.Services.Test [InlineData("scheme://host/path/", "scheme://host/path/", "")] [InlineData("scheme://host/path/", "scheme://host/path/more", "more")] [InlineData("scheme://host/path/", "scheme://host/path", "")] + [InlineData("scheme://host/path/", "scheme://host/path#hash", "#hash")] + [InlineData("scheme://host/path/", "scheme://host/path/#hash", "#hash")] + [InlineData("scheme://host/path/", "scheme://host/path/more#hash", "more#hash")] public void ComputesCorrectValidBaseRelativePaths(string baseUri, string absoluteUri, string expectedResult) { var actualResult = _uriHelper.ToBaseRelativePath(baseUri, absoluteUri); diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs index 19c9249e47..72db875d48 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.Manual.cs @@ -49,6 +49,32 @@ namespace Microsoft.AspNetCore.Components.RenderTree // Built-in components: https://github.com/aspnet/AspNetCore/issues/8825 namespace Microsoft.AspNetCore.Components { + public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase + { + public AuthorizeView() { } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; } + } + + public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable + { + public CascadingAuthenticationState() { } + [Microsoft.AspNetCore.Components.ParameterAttribute] + public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } private set { throw null; } } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { } + protected override void OnInit() { } + void System.IDisposable.Dispose() { } + } + public partial class CascadingValue : Microsoft.AspNetCore.Components.IComponent { public CascadingValue() { } diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index 1729a18ed1..a1b3d1e2f8 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -16,21 +16,6 @@ namespace Microsoft.AspNetCore.Components public abstract System.Threading.Tasks.Task GetAuthenticationStateAsync(); protected void NotifyAuthenticationStateChanged(System.Threading.Tasks.Task task) { } } - public partial class AuthorizeView : Microsoft.AspNetCore.Components.ComponentBase - { - public AuthorizeView() { } - [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment Authorizing { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment NotAuthorized { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - protected override System.Threading.Tasks.Task OnParametersSetAsync() { throw null; } - } [Microsoft.AspNetCore.Components.BindElementAttribute("select", null, "value", "onchange")] [Microsoft.AspNetCore.Components.BindElementAttribute("textarea", null, "value", "onchange")] [Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange")] @@ -85,15 +70,6 @@ namespace Microsoft.AspNetCore.Components public static System.Action SetValueHandler(System.Action setter, string existingValue) { throw null; } public static System.Action SetValueHandler(System.Action setter, T existingValue) { throw null; } } - public partial class CascadingAuthenticationState : Microsoft.AspNetCore.Components.ComponentBase, System.IDisposable - { - public CascadingAuthenticationState() { } - [Microsoft.AspNetCore.Components.ParameterAttribute] - public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) { } - protected override void OnInit() { } - void System.IDisposable.Dispose() { } - } [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=false)] public sealed partial class CascadingParameterAttribute : System.Attribute { diff --git a/src/Components/Components/src/Auth/AuthenticationState.cs b/src/Components/Components/src/Auth/AuthenticationState.cs index 2a145d44f3..d90090c7c8 100644 --- a/src/Components/Components/src/Auth/AuthenticationState.cs +++ b/src/Components/Components/src/Auth/AuthenticationState.cs @@ -11,11 +11,6 @@ namespace Microsoft.AspNetCore.Components /// public class AuthenticationState { - /// - /// Gets a that describes the current user. - /// - public ClaimsPrincipal User { get; } - /// /// Constructs an instance of . /// @@ -24,5 +19,10 @@ namespace Microsoft.AspNetCore.Components { User = user ?? throw new ArgumentNullException(nameof(user)); } + + /// + /// Gets a that describes the current user. + /// + public ClaimsPrincipal User { get; } } } diff --git a/src/Components/Components/src/Auth/AuthenticationStateProvider.cs b/src/Components/Components/src/Auth/AuthenticationStateProvider.cs index ffd2fc252a..e9e3e3c772 100644 --- a/src/Components/Components/src/Auth/AuthenticationStateProvider.cs +++ b/src/Components/Components/src/Auth/AuthenticationStateProvider.cs @@ -12,19 +12,16 @@ namespace Microsoft.AspNetCore.Components public abstract class AuthenticationStateProvider { /// - /// Gets an instance that describes - /// the current user. + /// Asynchronously gets an that describes the current user. /// - /// An instance that describes the current user. + /// A task that, when resolved, gives an instance that describes the current user. public abstract Task GetAuthenticationStateAsync(); /// /// An event that provides notification when the /// has changed. For example, this event may be raised if a user logs in or out. /// -#pragma warning disable 0067 // "Never used" (it's only raised by subclasses) public event AuthenticationStateChangedHandler AuthenticationStateChanged; -#pragma warning restore 0067 /// /// Raises the event. diff --git a/src/Components/Components/src/UriHelperBase.cs b/src/Components/Components/src/UriHelperBase.cs index 41af519eca..3b8f091045 100644 --- a/src/Components/Components/src/UriHelperBase.cs +++ b/src/Components/Components/src/UriHelperBase.cs @@ -154,7 +154,10 @@ namespace Microsoft.AspNetCore.Components // baseUri ends with a slash), and from that we return "something" return locationAbsolute.Substring(baseUri.Length); } - else if ($"{locationAbsolute}/".Equals(baseUri, StringComparison.Ordinal)) + + var hashIndex = locationAbsolute.IndexOf('#'); + var locationAbsoluteNoHash = hashIndex < 0 ? locationAbsolute : locationAbsolute.Substring(0, hashIndex); + if ($"{locationAbsoluteNoHash}/".Equals(baseUri, StringComparison.Ordinal)) { // Special case: for the base URI "/something/", if you're at // "/something" then treat it as if you were at "/something/" (i.e., @@ -162,7 +165,7 @@ namespace Microsoft.AspNetCore.Components // whether the server would return the same page whether or not the // slash is present, but ASP.NET Core at least does by default when // using PathBase. - return string.Empty; + return locationAbsolute.Substring(baseUri.Length - 1); } var message = $"The URI '{locationAbsolute}' is not contained by the base URI '{baseUri}'."; diff --git a/src/Components/test/E2ETest/Infrastructure/BasicTestAppTestBase.cs b/src/Components/test/E2ETest/Infrastructure/BasicTestAppTestBase.cs index d4933b858e..62d62f0d68 100644 --- a/src/Components/test/E2ETest/Infrastructure/BasicTestAppTestBase.cs +++ b/src/Components/test/E2ETest/Infrastructure/BasicTestAppTestBase.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure public class BasicTestAppTestBase : ServerTestBase> { public string ServerPathBase - => "/subdir" + (_serverFixture.UsingAspNetHost ? "#server" : ""); + => "/subdir" + (_serverFixture.ExecutionMode == ExecutionMode.Server ? "#server" : ""); public BasicTestAppTestBase( BrowserFixture browserFixture, diff --git a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs index 9fb9863f60..759b871852 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs @@ -9,7 +9,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures : ServerFixture { public string PathBase { get; set; } - public bool UsingAspNetHost { get; private set; } + + public ExecutionMode ExecutionMode { get; set; } = ExecutionMode.Client; private AspNetSiteServerFixture.BuildWebHost _buildWebHostMethod; private IDisposable _serverToDispose; @@ -18,7 +19,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures { _buildWebHostMethod = buildWebHostMethod ?? throw new ArgumentNullException(nameof(buildWebHostMethod)); - UsingAspNetHost = true; } protected override string StartAndGetRootUri() @@ -46,4 +46,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures _serverToDispose?.Dispose(); } } + + public enum ExecutionMode { Client, Server } } diff --git a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs index be5e996196..b7b6d361c8 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs @@ -8,7 +8,7 @@ using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; -namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests +namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { public class PrerenderingTest : ServerTestBase { diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs index e867e03a9b..c8e34f36cd 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests public static ToggleExecutionModeServerFixture WithServerExecution(this ToggleExecutionModeServerFixture serverFixture) { serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost); + serverFixture.ExecutionMode = ExecutionMode.Server; return serverFixture; } } diff --git a/src/Components/test/E2ETest/ServerExecutionTests/TestSubclasses.cs b/src/Components/test/E2ETest/ServerExecutionTests/TestSubclasses.cs index 9265718d54..1f16095af5 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/TestSubclasses.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/TestSubclasses.cs @@ -81,4 +81,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { } } + + public class ServerAuthTest : AuthTest + { + public ServerAuthTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } } diff --git a/src/Components/test/E2ETest/Tests/AuthTest.cs b/src/Components/test/E2ETest/Tests/AuthTest.cs new file mode 100644 index 0000000000..cc3139b6ab --- /dev/null +++ b/src/Components/test/E2ETest/Tests/AuthTest.cs @@ -0,0 +1,92 @@ +// 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 BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class AuthTest : BasicTestAppTestBase + { + // These strings correspond to the links in BasicTestApp\AuthTest\Links.razor + const string CascadingAuthenticationStateLink = "Cascading authentication state"; + const string AuthorizeViewCases = "AuthorizeView cases"; + + public AuthTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + // Normally, the E2E tests use the Blazor dev server if they are testing + // client-side execution. But for the auth tests, we always have to run + // in "hosted on ASP.NET Core" mode, because we get the auth state from it. + serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost); + } + + [Fact] + public void CascadingAuthenticationState_Unauthenticated() + { + SignInAs(null); + + var appElement = MountAndNavigateToAuthTest(CascadingAuthenticationStateLink); + + Browser.Equal("False", () => appElement.FindElement(By.Id("identity-authenticated")).Text); + Browser.Equal(string.Empty, () => appElement.FindElement(By.Id("identity-name")).Text); + Browser.Equal("(none)", () => appElement.FindElement(By.Id("test-claim")).Text); + } + + [Fact] + public void CascadingAuthenticationState_Authenticated() + { + SignInAs("someone cool"); + + var appElement = MountAndNavigateToAuthTest(CascadingAuthenticationStateLink); + + Browser.Equal("True", () => appElement.FindElement(By.Id("identity-authenticated")).Text); + Browser.Equal("someone cool", () => appElement.FindElement(By.Id("identity-name")).Text); + Browser.Equal("Test claim value", () => appElement.FindElement(By.Id("test-claim")).Text); + } + + [Fact] + public void AuthorizeViewCases_NoAuthorizationRule_Unauthenticated() + { + SignInAs(null); + MountAndNavigateToAuthTest(AuthorizeViewCases); + WaitUntilExists(By.CssSelector("#no-authorization-rule .not-authorized")); + } + + [Fact] + public void AuthorizeViewCases_NoAuthorizationRule_Authenticated() + { + SignInAs("Some User"); + var appElement = MountAndNavigateToAuthTest(AuthorizeViewCases); + Browser.Equal("Welcome, Some User!", () => + appElement.FindElement(By.CssSelector("#no-authorization-rule .authorized")).Text); + } + + IWebElement MountAndNavigateToAuthTest(string authLinkText) + { + Navigate(ServerPathBase); + var appElement = MountTestComponent(); + WaitUntilExists(By.Id("auth-links")); + appElement.FindElement(By.LinkText(authLinkText)).Click(); + return appElement; + } + + void SignInAs(string usernameOrNull) + { + const string authenticationPageUrl = "/Authentication"; + var baseRelativeUri = usernameOrNull == null + ? $"{authenticationPageUrl}?signout=true" + : $"{authenticationPageUrl}?username={usernameOrNull}"; + Navigate(baseRelativeUri); + WaitUntilExists(By.CssSelector("h1#authentication")); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/BindTest.cs b/src/Components/test/E2ETest/Tests/BindTest.cs index b08a315441..51fcc9ea9d 100644 --- a/src/Components/test/E2ETest/Tests/BindTest.cs +++ b/src/Components/test/E2ETest/Tests/BindTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { // On WebAssembly, page reloads are expensive so skip if possible - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); MountTestComponent(); WaitUntilExists(By.Id("bind-cases")); } diff --git a/src/Components/test/E2ETest/Tests/CascadingValueTest.cs b/src/Components/test/E2ETest/Tests/CascadingValueTest.cs index 8102be1e58..e4642fa98c 100644 --- a/src/Components/test/E2ETest/Tests/CascadingValueTest.cs +++ b/src/Components/test/E2ETest/Tests/CascadingValueTest.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); MountTestComponent(); } diff --git a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs index 5c8e194cac..c88d2997c6 100644 --- a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); } [Fact] diff --git a/src/Components/test/E2ETest/Tests/EventBubblingTest.cs b/src/Components/test/E2ETest/Tests/EventBubblingTest.cs index a15d1b31d4..f45e3d106a 100644 --- a/src/Components/test/E2ETest/Tests/EventBubblingTest.cs +++ b/src/Components/test/E2ETest/Tests/EventBubblingTest.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); MountTestComponent(); WaitUntilExists(By.Id("event-bubbling")); } diff --git a/src/Components/test/E2ETest/Tests/EventCallbackTest.cs b/src/Components/test/E2ETest/Tests/EventCallbackTest.cs index 4e5472a88a..01d53d6b20 100644 --- a/src/Components/test/E2ETest/Tests/EventCallbackTest.cs +++ b/src/Components/test/E2ETest/Tests/EventCallbackTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { // On WebAssembly, page reloads are expensive so skip if possible - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); MountTestComponent(); } diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index 88f2a0d9fb..9f1083af20 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { // On WebAssembly, page reloads are expensive so skip if possible - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); } [Fact] diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs index 4b82c98096..7ab262c465 100644 --- a/src/Components/test/E2ETest/Tests/InteropTest.cs +++ b/src/Components/test/E2ETest/Tests/InteropTest.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests // Include the sync assertions only when running under WebAssembly var expectedValues = expectedAsyncValues; - if (!_serverFixture.UsingAspNetHost) + if (_serverFixture.ExecutionMode == ExecutionMode.Client) { foreach (var kvp in expectedSyncValues) { diff --git a/src/Components/test/E2ETest/Tests/KeyTest.cs b/src/Components/test/E2ETest/Tests/KeyTest.cs index b7a5519694..2cb0e58d18 100644 --- a/src/Components/test/E2ETest/Tests/KeyTest.cs +++ b/src/Components/test/E2ETest/Tests/KeyTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests protected override void InitializeAsyncCore() { // On WebAssembly, page reloads are expensive so skip if possible - Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost); + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); } [Fact] diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/AuthHome.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthHome.razor new file mode 100644 index 0000000000..863f865a98 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthHome.razor @@ -0,0 +1,3 @@ +@page "/AuthHome" + +Select an auth test below. diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor new file mode 100644 index 0000000000..299a5beb87 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthRouter.razor @@ -0,0 +1,30 @@ +@using Microsoft.AspNetCore.Components.Routing +@inject IUriHelper UriHelper + +@* + This router is independent of any other router that may exist within the same project. + It exists so that (1) we can easily have multiple test cases that depend on the + CascadingAuthenticationState, and (2) we can test the integration between the router + and @page authorization rules. +*@ + + + + + +
+ + +@functions { + protected override void OnInit() + { + // Start at AuthHome, not at any other component in the same app that happens to + // register itself for the route "" + var absoluteUriPath = new Uri(UriHelper.GetAbsoluteUri()).GetLeftPart(UriPartial.Path); + var relativeUri = UriHelper.ToBaseRelativePath(UriHelper.GetBaseUri(), absoluteUriPath); + if (relativeUri == string.Empty) + { + UriHelper.NavigateTo("AuthHome"); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/AuthorizeViewCases.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthorizeViewCases.razor new file mode 100644 index 0000000000..d78c70f72a --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/AuthorizeViewCases.razor @@ -0,0 +1,17 @@ +@page "/AuthorizeViewCases" + +
+

Scenario: No authorization rule

+ + + +

Authorizing...

+
+ +

Welcome, @context.User.Identity.Name!

+
+ +

You're not logged in.

+
+
+
diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/CascadingAuthenticationStateConsumer.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/CascadingAuthenticationStateConsumer.razor new file mode 100644 index 0000000000..8113e1080f --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/CascadingAuthenticationStateConsumer.razor @@ -0,0 +1,47 @@ +@page "/CascadingAuthenticationStateConsumer" +@using System.Security.Claims + +

Cascading authentication state

+ +@if (user == null) +{ + Requesting authentication state... +} +else +{ +

+ Authenticated: + @user.Identity.IsAuthenticated +

+ +

+ Name: + @user.Identity.Name +

+ +

+ Test claim: + @if (user.HasClaim(TestClaimPredicate) == true) + { + @user.Claims.Single(c => TestClaimPredicate(c)).Value + } + else + { + (none) + } +

+} + +@functions +{ + static Predicate TestClaimPredicate = c => c.Type == "test-claim"; + + ClaimsPrincipal user; + + [CascadingParameter] Task AuthenticationStateTask { get; set; } + + protected override async Task OnParametersSetAsync() + { + user = (await AuthenticationStateTask).User; + } +} diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/ClientSideAuthenticationStateData.cs b/src/Components/test/testassets/BasicTestApp/AuthTest/ClientSideAuthenticationStateData.cs new file mode 100644 index 0000000000..15178b5d82 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/ClientSideAuthenticationStateData.cs @@ -0,0 +1,17 @@ +// 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; + +namespace BasicTestApp.AuthTest +{ + // DTO shared between server and client + public class ClientSideAuthenticationStateData + { + public bool IsAuthenticated { get; set; } + + public string UserName { get; set; } + + public Dictionary ExposedClaims { get; set; } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/Links.razor b/src/Components/test/testassets/BasicTestApp/AuthTest/Links.razor new file mode 100644 index 0000000000..70f524a763 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/Links.razor @@ -0,0 +1,8 @@ +@using Microsoft.AspNetCore.Components.Routing + + +

To change the underlying authentication state, go here.

diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs b/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs new file mode 100644 index 0000000000..08639f9254 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.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 System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; + +namespace BasicTestApp.AuthTest +{ + // This is intended to be similar to the authentication stateprovider included by default + // with the client-side Blazor "Hosted in ASP.NET Core" template + public class ServerAuthenticationStateProvider : AuthenticationStateProvider + { + private readonly HttpClient _httpClient; + + public ServerAuthenticationStateProvider(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public override async Task GetAuthenticationStateAsync() + { + var uri = new Uri(_httpClient.BaseAddress, "/api/User"); + var data = await _httpClient.GetJsonAsync(uri.AbsoluteUri); + ClaimsIdentity identity; + if (data.IsAuthenticated) + { + var claims = new[] { new Claim(ClaimTypes.Name, data.UserName) } + .Concat(data.ExposedClaims.Select(c => new Claim(c.Key, c.Value))); + identity = new ClaimsIdentity(claims, "Server authentication"); + } + else + { + identity = new ClaimsIdentity(); + } + + return new AuthenticationState(new ClaimsPrincipal(identity)); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 7348a5626f..7570d92e99 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -54,6 +54,8 @@ + + @if (SelectedComponentType != null) diff --git a/src/Components/test/testassets/BasicTestApp/Startup.cs b/src/Components/test/testassets/BasicTestApp/Startup.cs index 0f509b98e8..73084e0260 100644 --- a/src/Components/test/testassets/BasicTestApp/Startup.cs +++ b/src/Components/test/testassets/BasicTestApp/Startup.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.Runtime.InteropServices; +using BasicTestApp.AuthTest; using Microsoft.AspNetCore.Blazor.Http; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Builder; using Microsoft.Extensions.DependencyInjection; @@ -12,6 +14,7 @@ namespace BasicTestApp { public void ConfigureServices(IServiceCollection services) { + services.AddSingleton(); } public void Configure(IComponentsApplicationBuilder app) diff --git a/src/Components/test/testassets/TestServer/Components.TestServer.csproj b/src/Components/test/testassets/TestServer/Components.TestServer.csproj index 3566f065b3..bf0a093d11 100644 --- a/src/Components/test/testassets/TestServer/Components.TestServer.csproj +++ b/src/Components/test/testassets/TestServer/Components.TestServer.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 @@ -6,6 +6,7 @@ + diff --git a/src/Components/test/testassets/TestServer/Controllers/UserController.cs b/src/Components/test/testassets/TestServer/Controllers/UserController.cs new file mode 100644 index 0000000000..16764e4080 --- /dev/null +++ b/src/Components/test/testassets/TestServer/Controllers/UserController.cs @@ -0,0 +1,28 @@ +using System.Linq; +using BasicTestApp.AuthTest; +using Microsoft.AspNetCore.Mvc; + +namespace Components.TestServer.Controllers +{ + [Route("api/[controller]")] + public class UserController : Controller + { + // GET api/user + [HttpGet] + public ClientSideAuthenticationStateData Get() + { + // Servers are not expected to expose everything from the server-side ClaimsPrincipal + // to the client. It's up to the developer to choose what kind of authentication state + // data is needed on the client so it can display suitable options in the UI. + + return new ClientSideAuthenticationStateData + { + IsAuthenticated = User.Identity.IsAuthenticated, + UserName = User.Identity.Name, + ExposedClaims = User.Claims + .Where(c => c.Type == "test-claim") + .ToDictionary(c => c.Type, c => c.Value) + }; + } + } +} diff --git a/src/Components/test/testassets/TestServer/Pages/Authentication.cshtml b/src/Components/test/testassets/TestServer/Pages/Authentication.cshtml new file mode 100644 index 0000000000..3f6cbdefd5 --- /dev/null +++ b/src/Components/test/testassets/TestServer/Pages/Authentication.cshtml @@ -0,0 +1,72 @@ +@page +@using Microsoft.AspNetCore.Authentication +@using System.Security.Claims + + + + Authentication + + +

Authentication

+

+ This is a completely fake login mechanism for automated test purposes. + It accepts any username, with no password. +

+

+ Obviously you should not do this in real applications. + See also: documentation on configuring a real login system. +

+ +
+

Sign in

+ + @* Do not use method="get" for real login forms. This is just to simplify E2E tests. *@ +
+

+ User name: + +

+

+ +

+
+
+ +
+

Status

+

+ Authenticated: @User.Identity.IsAuthenticated + Username: @User.Identity.Name +

+ Sign out +
+ + + +@functions { + public async Task OnGet() + { + if (Request.Query["signout"] == "true") + { + await HttpContext.SignOutAsync(); + return Redirect("Authentication"); + } + + var username = Request.Query["username"]; + if (!string.IsNullOrEmpty(username)) + { + var claims = new List + { + new Claim(ClaimTypes.Name, username), + new Claim("test-claim", "Test claim value"), + }; + + await HttpContext.SignInAsync( + new ClaimsPrincipal(new ClaimsIdentity(claims, "FakeAuthenticationType"))); + + return Redirect("Authentication"); + } + + return Page(); + } +} diff --git a/src/Components/test/testassets/TestServer/Startup.cs b/src/Components/test/testassets/TestServer/Startup.cs index cf7ea69d32..26da1af8e0 100644 --- a/src/Components/test/testassets/TestServer/Startup.cs +++ b/src/Components/test/testassets/TestServer/Startup.cs @@ -1,4 +1,5 @@ using BasicTestApp; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Hosting; @@ -28,6 +29,7 @@ namespace TestServer options.AddPolicy("AllowAll", _ => { /* Controlled below */ }); }); services.AddServerSideBlazor(); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +52,7 @@ namespace TestServer .AllowCredentials(); }); + app.UseAuthentication(); // Mount the server-side Blazor app on /subdir app.Map("/subdir", subdirApp => @@ -70,6 +73,7 @@ namespace TestServer app.UseEndpoints(endpoints => { endpoints.MapControllers(); + endpoints.MapRazorPages(); }); // Separately, mount a prerendered server-side Blazor app on /prerendered From 194b0b45235fbd184a44b90e055fd6391ece18e9 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 20 May 2019 08:48:54 -0700 Subject: [PATCH 15/41] Update install JDK script an instructions to make it easier to use for local development (#10306) --- .azure/pipelines/jobs/default-build.yml | 7 +-- build.ps1 | 10 +++- docs/BuildFromSource.md | 4 ++ .../content/mssql/InstallSqlServerLocalDB.ps1 | 2 + eng/scripts/InstallJdk.ps1 | 49 ++++++++++++++----- global.json | 3 ++ 6 files changed, 56 insertions(+), 19 deletions(-) diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml index 3c8eaf7390..7de0612fad 100644 --- a/.azure/pipelines/jobs/default-build.yml +++ b/.azure/pipelines/jobs/default-build.yml @@ -121,7 +121,7 @@ jobs: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true TeamName: AspNetCore ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}: - JAVA_HOME: $(Agent.BuildDirectory)\.tools\jdk + JAVA_HOME: $(Agent.BuildDirectory)\.tools\jdk\win-x64 ${{ if or(ne(parameters.codeSign, true), ne(variables['System.TeamProject'], 'internal')) }}: _SignType: '' ${{ if and(eq(parameters.codeSign, true), eq(variables['System.TeamProject'], 'internal')) }}: @@ -146,10 +146,7 @@ jobs: command: custom arguments: 'locals all -clear' - ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}: - - powershell: | - ./eng/scripts/InstallJdk.ps1 '11.0.1' - Write-Host "##vso[task.prependpath]$env:JAVA_HOME\bin" - + - powershell: ./eng/scripts/InstallJdk.ps1 displayName: Install JDK 11 - ${{ if eq(parameters.isTestingJob, true) }}: - powershell: | diff --git a/build.ps1 b/build.ps1 index 2cf4e51853..75747822d1 100644 --- a/build.ps1 +++ b/build.ps1 @@ -306,7 +306,13 @@ $MSBuildArguments += "/p:TargetOsName=win" if (($All -or $BuildJava) -and -not $NoBuildJava) { $foundJdk = $false $javac = Get-Command javac -ErrorAction Ignore -CommandType Application - if ($env:JAVA_HOME) { + $localJdkPath = "$PSScriptRoot\.tools\jdk\win-x64\" + if (Test-Path "$localJdkPath\bin\javac.exe") { + $foundJdk = $true + Write-Host -f Magenta "Detected JDK in $localJdkPath (via local repo convention)" + $env:JAVA_HOME = $localJdkPath + } + elseif ($env:JAVA_HOME) { if (-not (Test-Path "${env:JAVA_HOME}\bin\javac.exe")) { Write-Error "The environment variable JAVA_HOME was set, but ${env:JAVA_HOME}\bin\javac.exe does not exist. Remove JAVA_HOME or update it to the correct location for the JDK. See https://www.bing.com/search?q=java_home for details." } @@ -337,7 +343,7 @@ if (($All -or $BuildJava) -and -not $NoBuildJava) { } if (-not $foundJdk) { - Write-Error "Could not find the JDK. See $PSScriptRoot\docs\BuildFromSource.md for details on this requirement." + Write-Error "Could not find the JDK. Either run $PSScriptRoot\eng\scripts\InstallJdk.ps1 to install for this repo, or install the JDK globally on your machine (see $PSScriptRoot\docs\BuildFromSource.md for details)." } } diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md index 0524cd80f9..82ed16067d 100644 --- a/docs/BuildFromSource.md +++ b/docs/BuildFromSource.md @@ -23,6 +23,10 @@ Building ASP.NET Core on Windows requires: * Java Development Kit 11 or newer. Either: * OpenJDK * Oracle's JDK + * To install a version of the JDK that will only be used by this repo, run [eng/scripts/InstallJdk.ps1](/eng/scripts/InstallJdk.ps1) + ```ps1 + PS> ./eng/scripts/InstalLJdk.ps1 + ``` ### macOS/Linux diff --git a/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1 b/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1 index 5af8fc5dac..8621112ac6 100644 --- a/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1 +++ b/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1 @@ -3,6 +3,8 @@ Installs SQL Server 2016 Express LocalDB on a machine. .DESCRIPTION This script installs Microsoft SQL Server 2016 Express LocalDB on a machine. +.PARAMETER Force + Force the script to run the MSI, even it it appears LocalDB is installed. .LINK https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-2016-express-localdb?view=sql-server-2016 https://docs.microsoft.com/en-us/sql/database-engine/install-windows/install-sql-server-from-the-command-prompt?view=sql-server-2016 diff --git a/eng/scripts/InstallJdk.ps1 b/eng/scripts/InstallJdk.ps1 index e56e768f4f..3602b6e734 100644 --- a/eng/scripts/InstallJdk.ps1 +++ b/eng/scripts/InstallJdk.ps1 @@ -1,27 +1,52 @@ - +<# +.SYNOPSIS + Installs JDK into a folder in this repo. +.DESCRIPTION + This script downloads an extracts the JDK. +.PARAMETER JdkVersion + The version of the JDK to install. If not set, the default value is read from global.json +.PARAMETER Force + Overwrite the existing installation +#> param( - [Parameter(Mandatory = $true)] - $JdkVersion + [string]$JdkVersion, + [switch]$Force ) - $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138 Set-StrictMode -Version 1 -if (-not $env:JAVA_HOME) { - throw 'You must set the JAVA_HOME environment variable to the destination of the JDK.' +$repoRoot = Resolve-Path "$PSScriptRoot\..\.." +$installDir = "$repoRoot\.tools\jdk\win-x64\" +$tempDir = "$repoRoot\obj" +if (-not $JdkVersion) { + $globalJson = Get-Content "$repoRoot\global.json" | ConvertFrom-Json + $JdkVersion = $globalJson.tools.jdk } -$repoRoot = Resolve-Path "$PSScriptRoot/../.." -$tempDir = "$repoRoot/obj" +if (Test-Path $installDir) { + if ($Force) { + Remove-Item -Force -Recurse $installDir + } + else { + Write-Host "The JDK already installed to $installDir. Exiting without action. Call this script again with -Force to overwrite." + exit 0 + } +} + +Remove-Item -Force -Recurse $tempDir -ErrorAction Ignore | out-null mkdir $tempDir -ea Ignore | out-null +mkdir $installDir -ea Ignore | out-null Write-Host "Starting download of JDK ${JdkVersion}" Invoke-WebRequest -UseBasicParsing -Uri "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/java/jdk-${JdkVersion}_windows-x64_bin.zip" -Out "$tempDir/jdk.zip" Write-Host "Done downloading JDK ${JdkVersion}" Expand-Archive "$tempDir/jdk.zip" -d "$tempDir/jdk/" Write-Host "Expanded JDK to $tempDir" -mkdir (split-path -parent $env:JAVA_HOME) -ea ignore | out-null -Write-Host "Installing JDK to $env:JAVA_HOME" -Move-Item "$tempDir/jdk/jdk-${jdkVersion}" $env:JAVA_HOME -Write-Host "Done installing JDK to $env:JAVA_HOME" +Write-Host "Installing JDK to $installDir" +Move-Item "$tempDir/jdk/jdk-${JdkVersion}/*" $installDir +Write-Host "Done installing JDK to $installDir" + +if ($env:TF_BUILD) { + Write-Host "##vso[task.prependpath]$installDir\bin" +} diff --git a/global.json b/global.json index 445956802e..20af51df71 100644 --- a/global.json +++ b/global.json @@ -2,6 +2,9 @@ "sdk": { "version": "3.0.100-preview5-011568" }, + "tools": { + "jdk": "11.0.3" + }, "msbuild-sdks": { "Yarn.MSBuild": "1.15.2", "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19262.1" From 630af4d319f6b8a2f633d463a2b549235569e6f0 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 20 May 2019 08:51:01 -0700 Subject: [PATCH 16/41] Fix detection of the JDK from a registry setting --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 75747822d1..dac40eb94a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -331,7 +331,7 @@ if (($All -or $BuildJava) -and -not $NoBuildJava) { try { $jdkVersion = (Get-Item HKLM:\SOFTWARE\JavaSoft\JDK | Get-ItemProperty -name CurrentVersion).CurrentVersion $javaHome = (Get-Item HKLM:\SOFTWARE\JavaSoft\JDK\$jdkVersion | Get-ItemProperty -Name JavaHome).JavaHome - if (Test-Path "${env:JAVA_HOME}\bin\java.exe") { + if (Test-Path "$javaHome\bin\javac.exe") { $env:JAVA_HOME = $javaHome Write-Host -f Magenta "Detected JDK $jdkVersion in $env:JAVA_HOME (via registry)" $foundJdk = $true From e92b4800dfe423c1583ec11897a172b3c7f5f3ee Mon Sep 17 00:00:00 2001 From: Alessio Franceschelli Date: Mon, 20 May 2019 16:57:55 +0100 Subject: [PATCH 17/41] Skip Java detection on restore if not building (#10368) --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index dac40eb94a..8029bc1267 100644 --- a/build.ps1 +++ b/build.ps1 @@ -303,7 +303,7 @@ $MSBuildArguments += "/p:_RunSign=$Sign" $MSBuildArguments += "/p:TargetArchitecture=$Architecture" $MSBuildArguments += "/p:TargetOsName=win" -if (($All -or $BuildJava) -and -not $NoBuildJava) { +if ($RunBuild -and ($All -or $BuildJava) -and -not $NoBuildJava) { $foundJdk = $false $javac = Get-Command javac -ErrorAction Ignore -CommandType Application $localJdkPath = "$PSScriptRoot\.tools\jdk\win-x64\" From 200c9e21bdeaa6257d3f617b06e28f6d1bf42c69 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 20 May 2019 09:10:12 -0700 Subject: [PATCH 18/41] Use attributes for application part discovery (#10271) * Use attributes for application part discovery Fixes https://github.com/aspnet/AspNetCore/issues/4332 --- eng/SharedFramework.External.props | 3 - .../ref/Microsoft.AspNetCore.Mvc.Core.csproj | 1 - ...osoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs | 9 +- .../ApplicationAssembliesProvider.cs | 295 ---------- .../ApplicationPartAttribute.cs | 33 ++ .../ApplicationPartManager.cs | 52 +- .../src/ApplicationParts/AssemblyPart.cs | 29 +- .../src/Microsoft.AspNetCore.Mvc.Core.csproj | 1 - .../ApplicationAssembliesProviderTest.cs | 544 ------------------ .../test/ApplicationParts/AssemblyPartTest.cs | 50 -- .../Microsoft.AspNetCore.Mvc.Core.Test.csproj | 3 +- ...etCore.Mvc.Razor.RuntimeCompilation.csproj | 1 + ....Razor.RuntimeCompilation.netcoreapp3.0.cs | 7 + .../src/AssemblyPartExtensions.cs | 38 ++ ...etCore.Mvc.Razor.RuntimeCompilation.csproj | 1 + .../src/RazorReferenceManager.cs | 22 +- .../test/AssemblyPartExtensionTest.cs | 65 +++ .../Microsoft.AspNetCore.Mvc.Testing.csproj | 1 + .../Microsoft.AspNetCore.Mvc.Testing.csproj | 1 + .../ApplicationAssembliesProviderTest.cs | 58 -- .../Properties/AssemblyInfo.cs | 7 + 21 files changed, 223 insertions(+), 998 deletions(-) delete mode 100644 src/Mvc/Mvc.Core/src/ApplicationParts/ApplicationAssembliesProvider.cs create mode 100644 src/Mvc/Mvc.Core/src/ApplicationParts/ApplicationPartAttribute.cs delete mode 100644 src/Mvc/Mvc.Core/test/ApplicationParts/ApplicationAssembliesProviderTest.cs create mode 100644 src/Mvc/Mvc.Razor.RuntimeCompilation/src/AssemblyPartExtensions.cs create mode 100644 src/Mvc/Mvc.Razor.RuntimeCompilation/test/AssemblyPartExtensionTest.cs delete mode 100644 src/Mvc/Mvc/test/ApplicationParts/ApplicationAssembliesProviderTest.cs create mode 100644 src/Mvc/test/WebSites/RazorPagesWebSite/Properties/AssemblyInfo.cs diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props index b8cf631738..d735b0764f 100644 --- a/eng/SharedFramework.External.props +++ b/eng/SharedFramework.External.props @@ -56,9 +56,6 @@ - - - + + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + + + + + + + https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json + https://dotnetfeed.blob.core.windows.net/arcade-validation/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json + https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json + + + + + + + + + + + + diff --git a/eng/common/PublishToSymbolServers.proj b/eng/common/PublishToSymbolServers.proj new file mode 100644 index 0000000000..5d55e312b0 --- /dev/null +++ b/eng/common/PublishToSymbolServers.proj @@ -0,0 +1,82 @@ + + + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + 3650 + true + false + + + + + + + + + + + + + + + + + diff --git a/eng/common/README.md b/eng/common/README.md new file mode 100644 index 0000000000..ff49c37152 --- /dev/null +++ b/eng/common/README.md @@ -0,0 +1,28 @@ +# Don't touch this folder + + uuuuuuuuuuuuuuuuuuuu + u" uuuuuuuuuuuuuuuuuu "u + u" u$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ + $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ + $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ + $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ + $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$" u" + "u """""""""""""""""" u" + """""""""""""""""""" + +!!! Changes made in this directory are subject to being overwritten by automation !!! + +The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. diff --git a/eng/common/SigningValidation.proj b/eng/common/SigningValidation.proj new file mode 100644 index 0000000000..7045fb6fb9 --- /dev/null +++ b/eng/common/SigningValidation.proj @@ -0,0 +1,83 @@ + + + + + + netcoreapp2.1 + + + + + + + + $(NuGetPackageRoot)Microsoft.DotNet.SignCheck\$(SignCheckVersion)\tools\Microsoft.DotNet.SignCheck.exe + + $(PackageBasePath) + signcheck.log + signcheck.errors.log + signcheck.exclusions.txt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/common/SourceLinkValidation.ps1 b/eng/common/SourceLinkValidation.ps1 new file mode 100644 index 0000000000..cb2d28cb99 --- /dev/null +++ b/eng/common/SourceLinkValidation.ps1 @@ -0,0 +1,184 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$true)][string] $SourceLinkToolPath, # Full path to directory where dotnet SourceLink CLI was installed + [Parameter(Mandatory=$true)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade + [Parameter(Mandatory=$true)][string] $GHCommit # GitHub commit SHA used to build the packages +) + +# Cache/HashMap (File -> Exist flag) used to consult whether a file exist +# in the repository at a specific commit point. This is populated by inserting +# all files present in the repo at a specific commit point. +$global:RepoFiles = @{} + +$ValidatePackage = { + param( + [string] $PackagePath # Full path to a Symbols.NuGet package + ) + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + throw "Input file does not exist: $PackagePath" + } + + # Extensions for which we'll look for SourceLink information + # For now we'll only care about Portable & Embedded PDBs + $RelevantExtensions = @(".dll", ".exe", ".pdb") + + Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + $FailedFiles = 0 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $FileName = $_.FullName + $Extension = [System.IO.Path]::GetExtension($_.Name) + $FakeName = -Join((New-Guid), $Extension) + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName + + # We ignore resource DLLs + if ($FileName.EndsWith(".resources.dll")) { + return + } + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + + $ValidateFile = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $RealPath, + [ref] $FailedFiles + ) + + # Makes easier to reference `sourcelink cli` + Push-Location $using:SourceLinkToolPath + + $SourceLinkInfos = .\sourcelink.exe print-urls $FullPath | Out-String + + if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { + $NumFailedLinks = 0 + + # We only care about Http addresses + $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches + + if ($Matches.Count -ne 0) { + $Matches.Value | + ForEach-Object { + $Link = $_ + $CommitUrl = -Join("https://raw.githubusercontent.com/", $using:GHRepoName, "/", $using:GHCommit, "/") + $FilePath = $Link.Replace($CommitUrl, "") + $Status = 200 + $Cache = $using:RepoFiles + + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + # Only GitHub links are valid + if ($Uri.AbsoluteURI -ne $null -and $Uri.Host -match "github") { + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + $Status = 0 + } + } + catch { + $Status = 0 + } + } + + if ($Status -ne 200) { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ + } + } + } + + if ($NumFailedLinks -ne 0) { + $FailedFiles.value++ + $global:LASTEXITCODE = 1 + } + } + + Pop-Location + } + + &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) + } + + $zip.Dispose() + + if ($FailedFiles -eq 0) { + Write-Host "Passed." + } +} + +function ValidateSourceLinkLinks { + if (!($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { + Write-Host "GHRepoName should be in the format /" + $global:LASTEXITCODE = 1 + return + } + + if (!($GHCommit -Match "^[0-9a-fA-F]{40}$")) { + Write-Host "GHCommit should be a 40 chars hexadecimal string" + $global:LASTEXITCODE = 1 + return + } + + $RepoTreeURL = -Join("https://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") + $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") + + try { + # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash + $Data = Invoke-WebRequest $RepoTreeURL | ConvertFrom-Json | Select-Object -ExpandProperty tree + + foreach ($file in $Data) { + $Extension = [System.IO.Path]::GetExtension($file.path) + + if ($CodeExtensions.Contains($Extension)) { + $RepoFiles[$file.path] = 1 + } + } + } + catch { + Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL" + $global:LASTEXITCODE = 1 + return + } + + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + # Process each NuGet package in parallel + $Jobs = @() + Get-ChildItem "$InputPath\*.symbols.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +Measure-Command { ValidateSourceLinkLinks } diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 new file mode 100644 index 0000000000..d7e3799ebd --- /dev/null +++ b/eng/common/build.ps1 @@ -0,0 +1,138 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug", + [string] $projects, + [string][Alias('v')]$verbosity = "minimal", + [string] $msbuildEngine = $null, + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch][Alias('r')]$restore, + [switch] $deployDeps, + [switch][Alias('b')]$build, + [switch] $rebuild, + [switch] $deploy, + [switch][Alias('t')]$test, + [switch] $integrationTest, + [switch] $performanceTest, + [switch] $sign, + [switch] $pack, + [switch] $publish, + [switch][Alias('bl')]$binaryLog, + [switch] $ci, + [switch] $prepareMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +. $PSScriptRoot\tools.ps1 + +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +} + +function InitializeCustomToolset { + if (-not $restore) { + return + } + + $script = Join-Path $EngRoot "restore-toolset.ps1" + + if (Test-Path $script) { + . $script + } +} + +function Build { + $toolsetBuildProj = InitializeToolset + InitializeCustomToolset + + $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "Build.binlog") } else { "" } + + if ($projects) { + # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. + # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. + [string[]] $msbuildArgs = $properties + $msbuildArgs += "/p:Projects=$projects" + $properties = $msbuildArgs + } + + MSBuild $toolsetBuildProj ` + $bl ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:Restore=$restore ` + /p:DeployDeps=$deployDeps ` + /p:Build=$build ` + /p:Rebuild=$rebuild ` + /p:Deploy=$deploy ` + /p:Test=$test ` + /p:Pack=$pack ` + /p:IntegrationTest=$integrationTest ` + /p:PerformanceTest=$performanceTest ` + /p:Sign=$sign ` + /p:Publish=$publish ` + @properties +} + +try { + if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + Print-Usage + exit 0 + } + + if ($ci) { + $binaryLog = $true + $nodeReuse = $false + } + + # Import custom tools configuration, if present in the repo. + # Note: Import in global scope so that the script set top-level variables without qualification. + $configureToolsetScript = Join-Path $EngRoot "configure-toolset.ps1" + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + } + + if (($restore) -and ($null -eq $env:DisableNativeToolsetInstalls)) { + InitializeNativeTools + } + + Build +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/build.sh b/eng/common/build.sh new file mode 100755 index 0000000000..ce846d888d --- /dev/null +++ b/eng/common/build.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash + +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u + +# Stop script if command returns non-zero exit code. +# Prevents hidden errors caused by missing error code propagation. +set -e + +usage() +{ + echo "Common settings:" + echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + echo " --binaryLog Create MSBuild binary log (short: -bl)" + echo " --help Print help and exit (short: -h)" + echo "" + + echo "Actions:" + echo " --restore Restore dependencies (short: -r)" + echo " --build Build solution (short: -b)" + echo " --rebuild Rebuild solution" + echo " --test Run all unit tests in the solution (short: -t)" + echo " --integrationTest Run all integration tests in the solution" + echo " --performanceTest Run all performance tests in the solution" + echo " --pack Package build outputs into NuGet packages and Willow components" + echo " --sign Sign build outputs" + echo " --publish Publish artifacts (e.g. symbols)" + echo "" + + echo "Advanced settings:" + echo " --projects Project or solution file(s) to build" + echo " --ci Set when running on CI server" + echo " --prepareMachine Prepare machine for CI run, clean up processes after build" + echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." + echo "Arguments can also be passed in with a single hyphen." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +restore=false +build=false +rebuild=false +test=false +integration_test=false +performance_test=false +pack=false +publish=false +sign=false +public=false +ci=false + +warn_as_error=true +node_reuse=true +binary_log=false + +projects='' +configuration='Debug' +prepare_machine=false +verbosity='minimal' + +properties='' + +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')" + case "$opt" in + -help|-h) + usage + exit 0 + ;; + -configuration|-c) + configuration=$2 + shift + ;; + -verbosity|-v) + verbosity=$2 + shift + ;; + -binarylog|-bl) + binary_log=true + ;; + -restore|-r) + restore=true + ;; + -build|-b) + build=true + ;; + -rebuild) + rebuild=true + ;; + -pack) + pack=true + ;; + -test|-t) + test=true + ;; + -integrationtest) + integration_test=true + ;; + -performancetest) + performance_test=true + ;; + -sign) + sign=true + ;; + -publish) + publish=true + ;; + -preparemachine) + prepare_machine=true + ;; + -projects) + projects=$2 + shift + ;; + -ci) + ci=true + ;; + -warnaserror) + warn_as_error=$2 + shift + ;; + -nodereuse) + node_reuse=$2 + shift + ;; + *) + properties="$properties $1" + ;; + esac + + shift +done + +if [[ "$ci" == true ]]; then + binary_log=true + node_reuse=false +fi + +. "$scriptroot/tools.sh" + +function InitializeCustomToolset { + local script="$eng_root/restore-toolset.sh" + + if [[ -a "$script" ]]; then + . "$script" + fi +} + +function Build { + InitializeToolset + InitializeCustomToolset + + if [[ ! -z "$projects" ]]; then + properties="$properties /p:Projects=$projects" + fi + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:\"$log_dir/Build.binlog\"" + fi + + MSBuild $_InitializeToolset \ + $bl \ + /p:Configuration=$configuration \ + /p:RepoRoot="$repo_root" \ + /p:Restore=$restore \ + /p:Build=$build \ + /p:Rebuild=$rebuild \ + /p:Test=$test \ + /p:Pack=$pack \ + /p:IntegrationTest=$integration_test \ + /p:PerformanceTest=$performance_test \ + /p:Sign=$sign \ + /p:Publish=$publish \ + $properties + + ExitWithExitCode 0 +} + +# Import custom tools configuration, if present in the repo. +configure_toolset_script="$eng_root/configure-toolset.sh" +if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi + +if [[ "$restore" == true && -z ${DisableNativeToolsetInstalls:-} ]]; then + InitializeNativeTools +fi + +Build diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh new file mode 100755 index 0000000000..1a02c0dec8 --- /dev/null +++ b/eng/common/cibuild.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file diff --git a/eng/common/cross/android/arm/toolchain.cmake b/eng/common/cross/android/arm/toolchain.cmake new file mode 100644 index 0000000000..a7e1c73501 --- /dev/null +++ b/eng/common/cross/android/arm/toolchain.cmake @@ -0,0 +1,41 @@ +set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) +set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) +set(CLR_CMAKE_PLATFORM_ANDROID "Android") + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR arm) + +## Specify the toolchain +set(TOOLCHAIN "arm-linux-androideabi") +set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) +set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) + +find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) +find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) +find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) +find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) +find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) +find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) +find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) + +add_compile_options(--sysroot=${CROSS_ROOTFS}) +add_compile_options(-fPIE) +add_compile_options(-mfloat-abi=soft) +include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/) +include_directories(SYSTEM ${CROSS_NDK_TOOLCHAIN}/include/c++/4.9.x/arm-linux-androideabi/) + +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) + +set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/android/arm64/toolchain.cmake b/eng/common/cross/android/arm64/toolchain.cmake new file mode 100644 index 0000000000..29415899c1 --- /dev/null +++ b/eng/common/cross/android/arm64/toolchain.cmake @@ -0,0 +1,42 @@ +set(CROSS_NDK_TOOLCHAIN $ENV{ROOTFS_DIR}/../) +set(CROSS_ROOTFS ${CROSS_NDK_TOOLCHAIN}/sysroot) +set(CLR_CMAKE_PLATFORM_ANDROID "Android") + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +## Specify the toolchain +set(TOOLCHAIN "aarch64-linux-android") +set(CMAKE_PREFIX_PATH ${CROSS_NDK_TOOLCHAIN}) +set(TOOLCHAIN_PREFIX ${TOOLCHAIN}-) + +find_program(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang) +find_program(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++) +find_program(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang) +find_program(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) +find_program(CMAKE_LD ${TOOLCHAIN_PREFIX}ar) +find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) +find_program(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) + +add_compile_options(--sysroot=${CROSS_ROOTFS}) +add_compile_options(-fPIE) + +## Needed for Android or bionic specific conditionals +add_compile_options(-D__ANDROID__) +add_compile_options(-D__BIONIC__) + +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -B ${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -L${CROSS_ROOTFS}/lib/${TOOLCHAIN}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} --sysroot=${CROSS_ROOTFS}") +set(CROSS_LINK_FLAGS "${CROSS_LINK_FLAGS} -fPIE -pie") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${CROSS_LINK_FLAGS}" CACHE STRING "" FORCE) + +set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/arm/sources.list.bionic b/eng/common/cross/arm/sources.list.bionic new file mode 100644 index 0000000000..2109557409 --- /dev/null +++ b/eng/common/cross/arm/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.jessie b/eng/common/cross/arm/sources.list.jessie new file mode 100644 index 0000000000..4d142ac9b1 --- /dev/null +++ b/eng/common/cross/arm/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (sid) # UNSTABLE +deb http://ftp.debian.org/debian/ sid main contrib non-free +deb-src http://ftp.debian.org/debian/ sid main contrib non-free diff --git a/eng/common/cross/arm/sources.list.trusty b/eng/common/cross/arm/sources.list.trusty new file mode 100644 index 0000000000..07d8f88d82 --- /dev/null +++ b/eng/common/cross/arm/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.xenial b/eng/common/cross/arm/sources.list.xenial new file mode 100644 index 0000000000..eacd86b7df --- /dev/null +++ b/eng/common/cross/arm/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.zesty b/eng/common/cross/arm/sources.list.zesty new file mode 100644 index 0000000000..ea2c14a787 --- /dev/null +++ b/eng/common/cross/arm/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/arm/trusty-lttng-2.4.patch b/eng/common/cross/arm/trusty-lttng-2.4.patch new file mode 100644 index 0000000000..8e4dd7ae71 --- /dev/null +++ b/eng/common/cross/arm/trusty-lttng-2.4.patch @@ -0,0 +1,71 @@ +From e72c9d7ead60e3317bd6d1fade995c07021c947b Mon Sep 17 00:00:00 2001 +From: Mathieu Desnoyers +Date: Thu, 7 May 2015 13:25:04 -0400 +Subject: [PATCH] Fix: building probe providers with C++ compiler + +Robert Daniels wrote: +> > I'm attempting to use lttng userspace tracing with a C++ application +> > on an ARM platform. I'm using GCC 4.8.4 on Linux 3.14 with the 2.6 +> > release of lttng. I've compiled lttng-modules, lttng-ust, and +> > lttng-tools and have been able to get a simple test working with C +> > code. When I attempt to run the hello.cxx test on my target it will +> > segfault. +> +> +> I spent a little time digging into this issue and finally discovered the +> cause of my segfault with ARM C++ tracepoints. +> +> There is a struct called 'lttng_event' in ust-events.h which contains an +> empty union 'u'. This was the cause of my issue. Under C, this empty union +> compiles to a zero byte member while under C++ it compiles to a one byte +> member, and in my case was four-byte aligned which caused my C++ code to +> have the 'cds_list_head node' offset incorrectly by four bytes. This lead +> to an incorrect linked list structure which caused my issue. +> +> Since this union is empty, I simply removed it from the struct and everything +> worked correctly. +> +> I don't know the history or purpose behind this empty union so I'd like to +> know if this is a safe fix. If it is I can submit a patch with the union +> removed. + +That's a very nice catch! + +We do not support building tracepoint probe provider with +g++ yet, as stated in lttng-ust(3): + +"- Note for C++ support: although an application instrumented with + tracepoints can be compiled with g++, tracepoint probes should be + compiled with gcc (only tested with gcc so far)." + +However, if it works fine with this fix, then I'm tempted to take it, +especially because removing the empty union does not appear to affect +the layout of struct lttng_event as seen from liblttng-ust, which must +be compiled with a C compiler, and from probe providers compiled with +a C compiler. So all we are changing is the layout of a probe provider +compiled with a C++ compiler, which is anyway buggy at the moment, +because it is not compatible with the layout expected by liblttng-ust +compiled with a C compiler. + +Reported-by: Robert Daniels +Signed-off-by: Mathieu Desnoyers +--- + include/lttng/ust-events.h | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/usr/include/lttng/ust-events.h b/usr/include/lttng/ust-events.h +index 328a875..3d7a274 100644 +--- a/usr/include/lttng/ust-events.h ++++ b/usr/include/lttng/ust-events.h +@@ -407,8 +407,6 @@ struct lttng_event { + void *_deprecated1; + struct lttng_ctx *ctx; + enum lttng_ust_instrumentation instrumentation; +- union { +- } u; + struct cds_list_head node; /* Event list in session */ + struct cds_list_head _deprecated2; + void *_deprecated3; +-- +2.7.4 + diff --git a/eng/common/cross/arm/trusty.patch b/eng/common/cross/arm/trusty.patch new file mode 100644 index 0000000000..2f2972f8eb --- /dev/null +++ b/eng/common/cross/arm/trusty.patch @@ -0,0 +1,97 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-03-28 06:04:42.000000000 +0900 ++++ b/usr/include/urcu/uatomic/generic.h 2017-02-13 10:35:21.189927116 +0900 +@@ -65,17 +65,17 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- return __sync_val_compare_and_swap_1(addr, old, _new); ++ return __sync_val_compare_and_swap_1((uint8_t *) addr, old, _new); + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t *) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t *) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: +- return __sync_val_compare_and_swap_8(addr, old, _new); ++ return __sync_val_compare_and_swap_8((uint64_t *) addr, old, _new); + #endif + } + _uatomic_link_error(); +@@ -100,20 +100,20 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- __sync_and_and_fetch_1(addr, val); ++ __sync_and_and_fetch_1((uint8_t *) addr, val); + return; + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- __sync_and_and_fetch_2(addr, val); ++ __sync_and_and_fetch_2((uint16_t *) addr, val); + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t *) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +- __sync_and_and_fetch_8(addr, val); ++ __sync_and_and_fetch_8((uint64_t *) addr, val); + return; + #endif + } +@@ -139,20 +139,20 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- __sync_or_and_fetch_1(addr, val); ++ __sync_or_and_fetch_1((uint8_t *) addr, val); + return; + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- __sync_or_and_fetch_2(addr, val); ++ __sync_or_and_fetch_2((uint16_t *) addr, val); + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t *) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +- __sync_or_and_fetch_8(addr, val); ++ __sync_or_and_fetch_8((uint64_t *) addr, val); + return; + #endif + } +@@ -180,17 +180,17 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- return __sync_add_and_fetch_1(addr, val); ++ return __sync_add_and_fetch_1((uint8_t *) addr, val); + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_add_and_fetch_2(addr, val); ++ return __sync_add_and_fetch_2((uint16_t *) addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t *) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: +- return __sync_add_and_fetch_8(addr, val); ++ return __sync_add_and_fetch_8((uint64_t *) addr, val); + #endif + } + _uatomic_link_error(); diff --git a/eng/common/cross/arm64/sources.list.bionic b/eng/common/cross/arm64/sources.list.bionic new file mode 100644 index 0000000000..2109557409 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.trusty b/eng/common/cross/arm64/sources.list.trusty new file mode 100644 index 0000000000..07d8f88d82 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm64/sources.list.xenial b/eng/common/cross/arm64/sources.list.xenial new file mode 100644 index 0000000000..eacd86b7df --- /dev/null +++ b/eng/common/cross/arm64/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm64/sources.list.zesty b/eng/common/cross/arm64/sources.list.zesty new file mode 100644 index 0000000000..ea2c14a787 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/armel/sources.list.jessie b/eng/common/cross/armel/sources.list.jessie new file mode 100644 index 0000000000..3d9c3059d8 --- /dev/null +++ b/eng/common/cross/armel/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (jessie) # Stable +deb http://ftp.debian.org/debian/ jessie main contrib non-free +deb-src http://ftp.debian.org/debian/ jessie main contrib non-free diff --git a/eng/common/cross/armel/tizen-build-rootfs.sh b/eng/common/cross/armel/tizen-build-rootfs.sh new file mode 100755 index 0000000000..87c48e78fb --- /dev/null +++ b/eng/common/cross/armel/tizen-build-rootfs.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -e + +__ARM_SOFTFP_CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__ARM_SOFTFP_CrossDir/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +# Clean-up (TODO-Cleanup: We may already delete $ROOTFS_DIR at ./cross/build-rootfs.sh.) +# hk0110 +if [ -d "$ROOTFS_DIR" ]; then + umount $ROOTFS_DIR/* + rm -rf $ROOTFS_DIR +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__ARM_SOFTFP_CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +rm ./usr/lib/libunwind.so +ln -s libunwind.so.8 ./usr/lib/libunwind.so +ln -sfn asm-arm ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_URL=http://download.tizen.org/releases/milestone/tizen +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize arm base" +fetch_tizen_pkgs_init standard base +Inform "fetch common packages" +fetch_tizen_pkgs armv7l gcc glibc glibc-devel libicu libicu-devel +fetch_tizen_pkgs noarch linux-glibc-devel +Inform "fetch coreclr packages" +fetch_tizen_pkgs armv7l lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel tizen-release lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs armv7l libcom_err libcom_err-devel zlib zlib-devel libopenssl libopenssl-devel krb5 krb5-devel libcurl libcurl-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard unified +Inform "fetch corefx packages" +fetch_tizen_pkgs armv7l gssdp gssdp-devel + diff --git a/eng/common/cross/armel/tizen/tizen-dotnet.ks b/eng/common/cross/armel/tizen/tizen-dotnet.ks new file mode 100644 index 0000000000..506d455bd4 --- /dev/null +++ b/eng/common/cross/armel/tizen/tizen-dotnet.ks @@ -0,0 +1,50 @@ +lang en_US.UTF-8 +keyboard us +timezone --utc Asia/Seoul + +part / --fstype="ext4" --size=3500 --ondisk=mmcblk0 --label rootfs --fsoptions=defaults,noatime + +rootpw tizen +desktop --autologinuser=root +user --name root --groups audio,video --password 'tizen' + +repo --name=standard --baseurl=http://download.tizen.org/releases/milestone/tizen/unified/latest/repos/standard/packages/ --ssl_verify=no +repo --name=base --baseurl=http://download.tizen.org/releases/milestone/tizen/base/latest/repos/standard/packages/ --ssl_verify=no + +%packages +tar +gzip + +sed +grep +gawk +perl + +binutils +findutils +util-linux +lttng-ust +userspace-rcu +procps-ng +tzdata +ca-certificates + + +### Core FX +libicu +libunwind +iputils +zlib +krb5 +libcurl +libopenssl + +%end + +%post + +### Update /tmp privilege +chmod 777 /tmp +#################################### + +%end diff --git a/eng/common/cross/armel/tizen/tizen.patch b/eng/common/cross/armel/tizen/tizen.patch new file mode 100644 index 0000000000..d223427c97 --- /dev/null +++ b/eng/common/cross/armel/tizen/tizen.patch @@ -0,0 +1,18 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) +diff -u -r a/usr/lib/libpthread.so b/usr/lib/libpthread.so +--- a/usr/lib/libpthread.so 2016-12-30 23:00:19.408951841 +0900 ++++ b/usr/lib/libpthread.so 2016-12-30 23:00:39.068951801 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libpthread.so.0 /usr/lib/libpthread_nonshared.a ) ++GROUP ( libpthread.so.0 libpthread_nonshared.a ) diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh new file mode 100755 index 0000000000..adceda877a --- /dev/null +++ b/eng/common/cross/build-android-rootfs.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -e +__NDK_Version=r14 + +usage() +{ + echo "Creates a toolchain and sysroot used for cross-compiling for Android." + echo. + echo "Usage: $0 [BuildArch] [ApiLevel]" + echo. + echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." + echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo. + echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" + echo "by setting the TOOLCHAIN_DIR environment variable" + echo. + echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," + echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." + echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.21-arm64. This file is to replace '/etc/os-release', which is not available for Android." + exit 1 +} + +__ApiLevel=21 # The minimum platform for arm64 is API level 21 +__BuildArch=arm64 +__AndroidArch=aarch64 +__AndroidToolchain=aarch64-linux-android + +for i in "$@" + do + lowerI="$(echo $i | awk '{print tolower($0)}')" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm64) + __BuildArch=arm64 + __AndroidArch=aarch64 + __AndroidToolchain=aarch64-linux-android + ;; + arm) + __BuildArch=arm + __AndroidArch=arm + __AndroidToolchain=arm-linux-androideabi + ;; + *[0-9]) + __ApiLevel=$i + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" + ;; + esac +done + +# Obtain the location of the bash script to figure out where the root of the repo is. +__CrossDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +__Android_Cross_Dir="$__CrossDir/android-rootfs" +__NDK_Dir="$__Android_Cross_Dir/android-ndk-$__NDK_Version" +__libunwind_Dir="$__Android_Cross_Dir/libunwind" +__lldb_Dir="$__Android_Cross_Dir/lldb" +__ToolchainDir="$__Android_Cross_Dir/toolchain/$__BuildArch" + +if [[ -n "$TOOLCHAIN_DIR" ]]; then + __ToolchainDir=$TOOLCHAIN_DIR +fi + +if [[ -n "$NDK_DIR" ]]; then + __NDK_Dir=$NDK_DIR +fi + +echo "Target API level: $__ApiLevel" +echo "Target architecture: $__BuildArch" +echo "NDK location: $__NDK_Dir" +echo "Target Toolchain location: $__ToolchainDir" + +# Download the NDK if required +if [ ! -d $__NDK_Dir ]; then + echo Downloading the NDK into $__NDK_Dir + mkdir -p $__NDK_Dir + wget -nv -nc --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__Android_Cross_Dir/android-ndk-$__NDK_Version-linux-x86_64.zip + unzip -q $__Android_Cross_Dir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__Android_Cross_Dir +fi + +if [ ! -d $__lldb_Dir ]; then + mkdir -p $__lldb_Dir + echo Downloading LLDB into $__lldb_Dir + wget -nv -nc --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__Android_Cross_Dir/lldb-2.3.3614996-linux-x86_64.zip + unzip -q $__Android_Cross_Dir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir +fi + +# Create the RootFS for both arm64 as well as aarch +rm -rf $__Android_Cross_Dir/toolchain + +echo Generating the $__BuildArch toolchain +$__NDK_Dir/build/tools/make_standalone_toolchain.py --arch $__BuildArch --api $__ApiLevel --install-dir $__ToolchainDir + +# Install the required packages into the toolchain +# TODO: Add logic to get latest pkg version instead of specific version number +rm -rf $__Android_Cross_Dir/deb/ +rm -rf $__Android_Cross_Dir/tmp + +mkdir -p $__Android_Cross_Dir/deb/ +mkdir -p $__Android_Cross_Dir/tmp/$arch/ +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libicu_60.2_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libicu_60.2_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libicu-dev_60.2_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libicu-dev_60.2_$__AndroidArch.deb + +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-glob-dev_0.4_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-glob-dev_0.4_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-glob_0.4_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-glob_0.4_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-support-dev_22_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-support-dev_22_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libandroid-support_22_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libandroid-support_22_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/liblzma-dev_5.2.3_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/liblzma-dev_5.2.3_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/liblzma_5.2.3_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/liblzma_5.2.3_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libunwind-dev_1.2.20170304_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libunwind-dev_1.2.20170304_$__AndroidArch.deb +wget -nv -nc http://termux.net/dists/stable/main/binary-$__AndroidArch/libunwind_1.2.20170304_$__AndroidArch.deb -O $__Android_Cross_Dir/deb/libunwind_1.2.20170304_$__AndroidArch.deb + +echo Unpacking Termux packages +dpkg -x $__Android_Cross_Dir/deb/libicu_60.2_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libicu-dev_60.2_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libandroid-glob-dev_0.4_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libandroid-glob_0.4_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libandroid-support-dev_22_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libandroid-support_22_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/liblzma-dev_5.2.3_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/liblzma_5.2.3_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libunwind-dev_1.2.20170304_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ +dpkg -x $__Android_Cross_Dir/deb/libunwind_1.2.20170304_$__AndroidArch.deb $__Android_Cross_Dir/tmp/$__AndroidArch/ + +cp -R $__Android_Cross_Dir/tmp/$__AndroidArch/data/data/com.termux/files/usr/* $__ToolchainDir/sysroot/usr/ + +# Generate platform file for build.sh script to assign to __DistroRid +echo "Generating platform file..." + +echo "RID=android.21-arm64" > $__ToolchainDir/sysroot/android_platform +echo Now run: +echo CONFIG_DIR=\`realpath cross/android/$__BuildArch\` ROOTFS_DIR=\`realpath $__ToolchainDir/sysroot\` ./build.sh cross $__BuildArch skipgenerateversion skipnuget cmakeargs -DENABLE_LLDBPLUGIN=0 + diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh new file mode 100755 index 0000000000..83ec39195c --- /dev/null +++ b/eng/common/cross/build-rootfs.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash + +usage() +{ + echo "Usage: $0 [BuildArch] [LinuxCodeName] [lldbx.y] [--skipunmount] --rootfs ]" + echo "BuildArch can be: arm(default), armel, arm64, x86" + echo "LinuxCodeName - optional, Code name for Linux, can be: trusty, xenial(default), zesty, bionic, alpine. If BuildArch is armel, LinuxCodeName is jessie(default) or tizen." + echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine" + echo "--skipunmount - optional, will skip the unmount of rootfs folder." + exit 1 +} + +__LinuxCodeName=xenial +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__InitialDir=$PWD +__BuildArch=arm +__UbuntuArch=armhf +__UbuntuRepo="http://ports.ubuntu.com/" +__LLDB_Package="liblldb-3.9-dev" +__SkipUnmount=0 + +# base development support +__UbuntuPackages="build-essential" + +__AlpinePackages="alpine-base" +__AlpinePackages+=" build-base" +__AlpinePackages+=" linux-headers" +__AlpinePackages+=" lldb-dev" +__AlpinePackages+=" llvm-dev" + +# symlinks fixer +__UbuntuPackages+=" symlinks" + +# CoreCLR and CoreFX dependencies +__UbuntuPackages+=" libicu-dev" +__UbuntuPackages+=" liblttng-ust-dev" +__UbuntuPackages+=" libunwind8-dev" + +__AlpinePackages+=" gettext-dev" +__AlpinePackages+=" icu-dev" +__AlpinePackages+=" libunwind-dev" +__AlpinePackages+=" lttng-ust-dev" + +# CoreFX dependencies +__UbuntuPackages+=" libcurl4-openssl-dev" +__UbuntuPackages+=" libkrb5-dev" +__UbuntuPackages+=" libssl-dev" +__UbuntuPackages+=" zlib1g-dev" + +__AlpinePackages+=" curl-dev" +__AlpinePackages+=" krb5-dev" +__AlpinePackages+=" openssl-dev" +__AlpinePackages+=" zlib-dev" + +__UnprocessedBuildArgs= +while :; do + if [ $# -le 0 ]; then + break + fi + + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm) + __BuildArch=arm + __UbuntuArch=armhf + __AlpineArch=armhf + __QEMUArch=arm + ;; + arm64) + __BuildArch=arm64 + __UbuntuArch=arm64 + __AlpineArch=aarch64 + __QEMUArch=aarch64 + ;; + armel) + __BuildArch=armel + __UbuntuArch=armel + __UbuntuRepo="http://ftp.debian.org/debian/" + __LinuxCodeName=jessie + ;; + x86) + __BuildArch=x86 + __UbuntuArch=i386 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + lldb3.6) + __LLDB_Package="lldb-3.6-dev" + ;; + lldb3.8) + __LLDB_Package="lldb-3.8-dev" + ;; + lldb3.9) + __LLDB_Package="liblldb-3.9-dev" + ;; + lldb4.0) + __LLDB_Package="liblldb-4.0-dev" + ;; + lldb5.0) + __LLDB_Package="liblldb-5.0-dev" + ;; + lldb6.0) + __LLDB_Package="liblldb-6.0-dev" + ;; + no-lldb) + unset __LLDB_Package + ;; + trusty) # Ubuntu 14.04 + if [ "$__LinuxCodeName" != "jessie" ]; then + __LinuxCodeName=trusty + fi + ;; + xenial) # Ubunry 16.04 + if [ "$__LinuxCodeName" != "jessie" ]; then + __LinuxCodeName=xenial + fi + ;; + zesty) # Ununtu 17.04 + if [ "$__LinuxCodeName" != "jessie" ]; then + __LinuxCodeName=zesty + fi + ;; + bionic) # Ubuntu 18.04 + if [ "$__LinuxCodeName" != "jessie" ]; then + __LinuxCodeName=bionic + fi + ;; + jessie) # Debian 8 + __LinuxCodeName=jessie + __UbuntuRepo="http://ftp.debian.org/debian/" + ;; + # TBD Stretch -> Debian 9, Buster -> Debian 10 + tizen) + if [ "$__BuildArch" != "armel" ]; then + echo "Tizen is available only for armel." + usage; + exit 1; + fi + __LinuxCodeName= + __UbuntuRepo= + __Tizen=tizen + ;; + alpine) + __LinuxCodeName=alpine + __UbuntuRepo= + ;; + --skipunmount) + __SkipUnmount=1 + ;; + --rootfsdir|-rootfsdir) + shift + __RootfsDir=$1 + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" + ;; + esac + + shift +done + +if [ "$__BuildArch" == "armel" ]; then + __LLDB_Package="lldb-3.5-dev" +fi +__UbuntuPackages+=" ${__LLDB_Package:-}" + +if [ -z "$__RootfsDir" ] && [ ! -z "$ROOTFS_DIR" ]; then + __RootfsDir=$ROOTFS_DIR +fi + +if [ -z "$__RootfsDir" ]; then + __RootfsDir="$__CrossDir/rootfs/$__BuildArch" +fi + +if [ -d "$__RootfsDir" ]; then + if [ $__SkipUnmount == 0 ]; then + umount $__RootfsDir/* + fi + rm -rf $__RootfsDir +fi + +if [[ "$__LinuxCodeName" == "alpine" ]]; then + __ApkToolsVersion=2.9.1 + __AlpineVersion=3.7 + __ApkToolsDir=$(mktemp -d) + wget https://github.com/alpinelinux/apk-tools/releases/download/v$__ApkToolsVersion/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -P $__ApkToolsDir + tar -xf $__ApkToolsDir/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -C $__ApkToolsDir + mkdir -p $__RootfsDir/usr/bin + cp -v /usr/bin/qemu-$__QEMUArch-static $__RootfsDir/usr/bin + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/main \ + -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/community \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/testing \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackages + rm -r $__ApkToolsDir +elif [[ -n $__LinuxCodeName ]]; then + qemu-debootstrap --arch $__UbuntuArch $__LinuxCodeName $__RootfsDir $__UbuntuRepo + cp $__CrossDir/$__BuildArch/sources.list.$__LinuxCodeName $__RootfsDir/etc/apt/sources.list + chroot $__RootfsDir apt-get update + chroot $__RootfsDir apt-get -f -y install + chroot $__RootfsDir apt-get -y install $__UbuntuPackages + chroot $__RootfsDir symlinks -cr /usr + + if [ $__SkipUnmount == 0 ]; then + umount $__RootfsDir/* + fi + + if [[ "$__BuildArch" == "arm" && "$__LinuxCodeName" == "trusty" ]]; then + pushd $__RootfsDir + patch -p1 < $__CrossDir/$__BuildArch/trusty.patch + patch -p1 < $__CrossDir/$__BuildArch/trusty-lttng-2.4.patch + popd + fi +elif [ "$__Tizen" == "tizen" ]; then + ROOTFS_DIR=$__RootfsDir $__CrossDir/$__BuildArch/tizen-build-rootfs.sh +else + echo "Unsupported target platform." + usage; + exit 1 +fi diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake new file mode 100644 index 0000000000..071d411241 --- /dev/null +++ b/eng/common/cross/toolchain.cmake @@ -0,0 +1,138 @@ +set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) + +set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 1) + +if(TARGET_ARCH_NAME STREQUAL "armel") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + set(TOOLCHAIN "arm-linux-gnueabi") + if("$ENV{__DistroRid}" MATCHES "tizen.*") + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/6.2.1") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(CMAKE_SYSTEM_PROCESSOR aarch64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) + set(TOOLCHAIN "aarch64-alpine-linux-musl") + else() + set(TOOLCHAIN "aarch64-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + set(CMAKE_SYSTEM_PROCESSOR i686) + set(TOOLCHAIN "i686-linux-gnu") +else() + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only armel, arm, arm64 and x86 are supported!") +endif() + +# Specify include paths +if(TARGET_ARCH_NAME STREQUAL "armel") + if(DEFINED TIZEN_TOOLCHAIN) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) + endif() +endif() + +# add_compile_param - adds only new options without duplicates. +# arg0 - list with result options, arg1 - list with new options. +# arg2 - optional argument, quick summary string for optional using CACHE FORCE mode. +macro(add_compile_param) + if(NOT ${ARGC} MATCHES "^(2|3)$") + message(FATAL_ERROR "Wrong using add_compile_param! Two or three parameters must be given! See add_compile_param description.") + endif() + foreach(OPTION ${ARGV1}) + if(NOT ${ARGV0} MATCHES "${OPTION}($| )") + set(${ARGV0} "${${ARGV0}} ${OPTION}") + if(${ARGC} EQUAL "3") # CACHE FORCE mode + set(${ARGV0} "${${ARGV0}}" CACHE STRING "${ARGV2}" FORCE) + endif() + endif() + endforeach() +endmacro() + +# Specify link flags +add_compile_param(CROSS_LINK_FLAGS "--sysroot=${CROSS_ROOTFS}") +add_compile_param(CROSS_LINK_FLAGS "--gcc-toolchain=${CROSS_ROOTFS}/usr") +add_compile_param(CROSS_LINK_FLAGS "--target=${TOOLCHAIN}") +add_compile_param(CROSS_LINK_FLAGS "-fuse-ld=gold") + +if(TARGET_ARCH_NAME STREQUAL "armel") + if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only + add_compile_param(CROSS_LINK_FLAGS "-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/lib") + add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib") + add_compile_param(CROSS_LINK_FLAGS "-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + add_compile_param(CROSS_LINK_FLAGS "-m32") +endif() + +add_compile_param(CMAKE_EXE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") +add_compile_param(CMAKE_SHARED_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") +add_compile_param(CMAKE_MODULE_LINKER_FLAGS "${CROSS_LINK_FLAGS}" "TOOLCHAIN_EXE_LINKER_FLAGS") + +# Specify compile options +add_compile_options("--sysroot=${CROSS_ROOTFS}") +add_compile_options("--target=${TOOLCHAIN}") +add_compile_options("--gcc-toolchain=${CROSS_ROOTFS}/usr") + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64)$") + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + add_compile_options(-mthumb) + add_compile_options(-mfpu=vfpv3) + if(TARGET_ARCH_NAME STREQUAL "armel") + add_compile_options(-mfloat-abi=softfp) + if(DEFINED TIZEN_TOOLCHAIN) + add_compile_options(-Wno-deprecated-declarations) # compile-time option + add_compile_options(-D__extern_always_inline=inline) # compile-time option + endif() + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + add_compile_options(-m32) + add_compile_options(-Wno-error=unused-command-line-argument) +endif() + +# Set LLDB include and library paths +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + if(TARGET_ARCH_NAME STREQUAL "x86") + set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") + else() # arm/armel case + set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") + endif() + if(LLVM_CROSS_DIR) + set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") + set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") + set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") + else() + if(TARGET_ARCH_NAME STREQUAL "x86") + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") + set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") + if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") + set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") + else() + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") + endif() + else() # arm/armel case + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") + endif() + endif() +endif() + +set(CMAKE_FIND_ROOT_PATH "${CROSS_ROOTFS}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/x86/sources.list.bionic b/eng/common/cross/x86/sources.list.bionic new file mode 100644 index 0000000000..a71ccadcff --- /dev/null +++ b/eng/common/cross/x86/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.trusty b/eng/common/cross/x86/sources.list.trusty new file mode 100644 index 0000000000..9b3085436e --- /dev/null +++ b/eng/common/cross/x86/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ trusty main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ trusty main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.xenial b/eng/common/cross/x86/sources.list.xenial new file mode 100644 index 0000000000..ad9c5a0144 --- /dev/null +++ b/eng/common/cross/x86/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 new file mode 100644 index 0000000000..81ffd16779 --- /dev/null +++ b/eng/common/darc-init.ps1 @@ -0,0 +1,32 @@ +param ( + $darcVersion = $null +) + +$verbosity = "m" +. $PSScriptRoot\tools.ps1 + +function InstallDarcCli ($darcVersion) { + $darcCliPackageName = "microsoft.dotnet.darc" + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = Invoke-Expression "& `"$dotnet`" tool list -g" + + if ($toolList -like "*$darcCliPackageName*") { + Invoke-Expression "& `"$dotnet`" tool uninstall $darcCliPackageName -g" + } + + # Until we can anonymously query the BAR API for the latest arcade-services + # build applied to the PROD channel, this is hardcoded. + if (-not $darcVersion) { + $darcVersion = '1.1.0-beta.19205.4' + } + + $arcadeServicesSource = 'https://dotnetfeed.blob.core.windows.net/dotnet-arcade/index.json' + + Write-Host "Installing Darc CLI version $darcVersion..." + Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." + Invoke-Expression "& `"$dotnet`" tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" +} + +InstallDarcCli $darcVersion diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh new file mode 100755 index 0000000000..bd7eb46398 --- /dev/null +++ b/eng/common/darc-init.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +darcVersion="1.1.0-beta.19205.4" + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | awk '{print tolower($0)}')" + case "$opt" in + --darcversion) + darcVersion=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +verbosity=m + +. "$scriptroot/tools.sh" + +function InstallDarcCli { + local darc_cli_package_name="microsoft.dotnet.darc" + + InitializeDotNetCli + local dotnet_root=$_InitializeDotNetCli + + local uninstall_command=`$dotnet_root/dotnet tool uninstall $darc_cli_package_name -g` + local tool_list=$($dotnet_root/dotnet tool list -g) + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + fi + + local arcadeServicesSource="https://dotnetfeed.blob.core.windows.net/dotnet-arcade/index.json" + + echo "Installing Darc CLI version $toolset_version..." + echo "You may need to restart your command shell if this is the first dotnet tool you have installed." + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) +} + +InstallDarcCli diff --git a/eng/common/dotnet-install.cmd b/eng/common/dotnet-install.cmd new file mode 100644 index 0000000000..b1c2642e76 --- /dev/null +++ b/eng/common/dotnet-install.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" \ No newline at end of file diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 new file mode 100644 index 0000000000..5987943fd6 --- /dev/null +++ b/eng/common/dotnet-install.ps1 @@ -0,0 +1,22 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = "minimal", + [string] $architecture = "", + [string] $version = "Latest", + [string] $runtime = "dotnet" +) + +. $PSScriptRoot\tools.ps1 + +try { + $dotnetRoot = Join-Path $RepoRoot ".dotnet" + InstallDotNet $dotnetRoot $version $architecture $runtime $true +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} + +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh new file mode 100755 index 0000000000..c3072c958a --- /dev/null +++ b/eng/common/dotnet-install.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +version='Latest' +architecture='' +runtime='dotnet' +while [[ $# > 0 ]]; do + opt="$(echo "$1" | awk '{print tolower($0)}')" + case "$opt" in + -version|-v) + shift + version="$1" + ;; + -architecture|-a) + shift + architecture="$1" + ;; + -runtime|-r) + shift + runtime="$1" + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + shift +done + +. "$scriptroot/tools.sh" +dotnetRoot="$repo_root/.dotnet" +InstallDotNet $dotnetRoot $version "$architecture" $runtime true || { + local exit_code=$? + echo "dotnet-install.sh failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code +} + +ExitWithExitCode 0 diff --git a/eng/common/generate-graph-files.ps1 b/eng/common/generate-graph-files.ps1 new file mode 100644 index 0000000000..a05b84f798 --- /dev/null +++ b/eng/common/generate-graph-files.ps1 @@ -0,0 +1,87 @@ +Param( + [Parameter(Mandatory=$true)][string] $barToken, # Token generated at https://maestro-prod.westus2.cloudapp.azure.com/Account/Tokens + [Parameter(Mandatory=$true)][string] $gitHubPat, # GitHub personal access token from https://github.com/settings/tokens (no auth scopes needed) + [Parameter(Mandatory=$true)][string] $azdoPat, # Azure Dev Ops tokens from https://dev.azure.com/dnceng/_details/security/tokens (code read scope needed) + [Parameter(Mandatory=$true)][string] $outputFolder, # Where the graphviz.txt file will be created + [string] $darcVersion = '1.1.0-beta.19175.6', # darc's version + [string] $graphvizVersion = '2.38', # GraphViz version + [switch] $includeToolset # Whether the graph should include toolset dependencies or not. i.e. arcade, optimization. For more about + # toolset dependencies see https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#toolset-vs-product-dependencies +) + +$ErrorActionPreference = "Stop" +. $PSScriptRoot\tools.ps1 + +Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") + +function CheckExitCode ([string]$stage) +{ + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-Host "Something failed in stage: '$stage'. Check for errors above. Exiting now..." + ExitWithExitCode $exitCode + } +} + +try { + Push-Location $PSScriptRoot + + Write-Host "Installing darc..." + . .\darc-init.ps1 -darcVersion $darcVersion + CheckExitCode "Running darc-init" + + $engCommonBaseDir = Join-Path $PSScriptRoot "native\" + $graphvizInstallDir = CommonLibrary\Get-NativeInstallDirectory + $nativeToolBaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external" + $installBin = Join-Path $graphvizInstallDir "bin" + + Write-Host "Installing dot..." + .\native\install-tool.ps1 -ToolName graphviz -InstallPath $installBin -BaseUri $nativeToolBaseUri -CommonLibraryDirectory $engCommonBaseDir -Version $graphvizVersion -Verbose + + $darcExe = "$env:USERPROFILE\.dotnet\tools" + $darcExe = Resolve-Path "$darcExe\darc.exe" + + Create-Directory $outputFolder + + # Generate 3 graph descriptions: + # 1. Flat with coherency information + # 2. Graphviz (dot) file + # 3. Standard dependency graph + $graphVizFilePath = "$outputFolder\graphviz.txt" + $graphVizImageFilePath = "$outputFolder\graph.png" + $normalGraphFilePath = "$outputFolder\graph-full.txt" + $flatGraphFilePath = "$outputFolder\graph-flat.txt" + $baseOptions = "get-dependency-graph --github-pat $gitHubPat --azdev-pat $azdoPat --password $barToken" + + if ($includeToolset) { + Write-Host "Toolsets will be included in the graph..." + $baseOptions += " --include-toolset" + } + + Write-Host "Generating standard dependency graph..." + Invoke-Expression "& `"$darcExe`" $baseOptions --output-file $normalGraphFilePath" + CheckExitCode "Generating normal dependency graph" + + Write-Host "Generating flat dependency graph and graphviz file..." + Invoke-Expression "& `"$darcExe`" $baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath" + CheckExitCode "Generating flat and graphviz dependency graph" + + Write-Host "Generating graph image $graphVizFilePath" + $dotFilePath = Join-Path $installBin "graphviz\$graphvizVersion\release\bin\dot.exe" + Invoke-Expression "& `"$dotFilePath`" -Tpng -o'$graphVizImageFilePath' `"$graphVizFilePath`"" + CheckExitCode "Generating graphviz image" + + Write-Host "'$graphVizFilePath', '$flatGraphFilePath', '$normalGraphFilePath' and '$graphVizImageFilePath' created!" +} +catch { + if (!$includeToolset) { + Write-Host "This might be a toolset repo which includes only toolset dependencies. " -NoNewline -ForegroundColor Yellow + Write-Host "Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again..." -ForegroundColor Yellow + } + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} finally { + Pop-Location +} \ No newline at end of file diff --git a/eng/common/helixpublish.proj b/eng/common/helixpublish.proj new file mode 100644 index 0000000000..d7f185856e --- /dev/null +++ b/eng/common/helixpublish.proj @@ -0,0 +1,26 @@ + + + + msbuild + + + + + %(Identity) + + + + + + $(WorkItemDirectory) + $(WorkItemCommand) + $(WorkItemTimeout) + + + + + + + + + diff --git a/eng/common/init-tools-native.cmd b/eng/common/init-tools-native.cmd new file mode 100644 index 0000000000..438cd548c4 --- /dev/null +++ b/eng/common/init-tools-native.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" +exit /b %ErrorLevel% \ No newline at end of file diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 new file mode 100644 index 0000000000..a4306bd37e --- /dev/null +++ b/eng/common/init-tools-native.ps1 @@ -0,0 +1,142 @@ +<# +.SYNOPSIS +Entry point script for installing native tools + +.DESCRIPTION +Reads $RepoRoot\global.json file to determine native assets to install +and executes installers for those tools + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER InstallDirectory +Directory to install native toolset. This is a command-line override for the default +Install directory precedence order: +- InstallDirectory command-line override +- NETCOREENG_INSTALL_DIRECTORY environment variable +- (default) %USERPROFILE%/.netcoreeng/native + +.PARAMETER Clean +Switch specifying to not install anything, but cleanup native asset folders + +.PARAMETER Force +Clean and then install tools + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.PARAMETER GlobalJsonFile +File path to global.json file + +.NOTES +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [string] $BaseUri = "https://netcorenativeassets.blob.core.windows.net/resource-packages/external", + [string] $InstallDirectory, + [switch] $Clean = $False, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [string] $GlobalJsonFile +) + +if (!$GlobalJsonFile) { + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName "global.json" +} + +Set-StrictMode -version 2.0 +$ErrorActionPreference="Stop" + +Import-Module -Name (Join-Path $PSScriptRoot "native\CommonLibrary.psm1") + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $EngCommonBaseDir = Join-Path $PSScriptRoot "native\" + $NativeBaseDir = $InstallDirectory + if (!$NativeBaseDir) { + $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory + } + $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir + $InstallBin = Join-Path $NativeBaseDir "bin" + $InstallerPath = Join-Path $EngCommonBaseDir "install-tool.ps1" + + # Process tools list + Write-Host "Processing $GlobalJsonFile" + If (-Not (Test-Path $GlobalJsonFile)) { + Write-Host "Unable to find '$GlobalJsonFile'" + exit 0 + } + $NativeTools = Get-Content($GlobalJsonFile) -Raw | + ConvertFrom-Json | + Select-Object -Expand "native-tools" -ErrorAction SilentlyContinue + if ($NativeTools) { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $LocalInstallerCommand = $InstallerPath + $LocalInstallerCommand += " -ToolName $ToolName" + $LocalInstallerCommand += " -InstallPath $InstallBin" + $LocalInstallerCommand += " -BaseUri $BaseUri" + $LocalInstallerCommand += " -CommonLibraryDirectory $EngCommonBaseDir" + $LocalInstallerCommand += " -Version $ToolVersion" + + if ($Verbose) { + $LocalInstallerCommand += " -Verbose" + } + if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { + if($Force) { + $LocalInstallerCommand += " -Force" + } + } + if ($Clean) { + $LocalInstallerCommand += " -Clean" + } + + Write-Verbose "Installing $ToolName version $ToolVersion" + Write-Verbose "Executing '$LocalInstallerCommand'" + Invoke-Expression "$LocalInstallerCommand" + if ($LASTEXITCODE -Ne "0") { + $errMsg = "$ToolName installation failed" + if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { + Write-Warning $errMsg + $toolInstallationFailure = $true + } else { + Write-Error $errMsg + exit 1 + } + } + } + + if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + exit 1 + } + } + else { + Write-Host "No native tools defined in global.json" + exit 0 + } + + if ($Clean) { + exit 0 + } + if (Test-Path $InstallBin) { + Write-Host "Native tools are available from" (Convert-Path -Path $InstallBin) + Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + } + else { + Write-Error "Native tools install directory does not exist, installation failed" + exit 1 + } + exit 0 +} +catch { + Write-Host $_ + Write-Host $_.Exception + exit 1 +} diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh new file mode 100755 index 0000000000..fc72d13948 --- /dev/null +++ b/eng/common/init-tools-native.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' +install_directory='' +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 +global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" +declare -A native_assets + +. $scriptroot/native/common-library.sh + +while (($# > 0)); do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installdirectory) + install_directory=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --installdirectory Directory to install native toolset." + echo " This is a command-line override for the default" + echo " Install directory precedence order:" + echo " - InstallDirectory command-line override" + echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" + echo " - (default) %USERPROFILE%/.netcoreeng/native" + echo "" + echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --force Clean and then install tools" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --baseuri Base URI for where to download native tools from" + echo " --downloadretries Number of times a download should be attempted" + echo " --retrywaittimeseconds Wait time between download attempts" + echo "" + exit 0 + ;; + esac +done + +function ReadGlobalJsonNativeTools { + # Get the native-tools section from the global.json. + local native_tools_section=$(cat $global_json_file | awk '/"native-tools"/,/}/') + # Only extract the contents of the object. + local native_tools_list=$(echo $native_tools_section | awk -F"[{}]" '{print $2}') + native_tools_list=${native_tools_list//[\" ]/} + native_tools_list=${native_tools_list//,/$'\n'} + native_tools_list="$(echo -e "${native_tools_list}" | tr -d '[:space:]')" + + local old_IFS=$IFS + while read -r line; do + # Lines are of the form: 'tool:version' + IFS=: + while read -r key value; do + native_assets[$key]=$value + done <<< "$line" + done <<< "$native_tools_list" + IFS=$old_IFS + + return 0; +} + +native_base_dir=$install_directory +if [[ -z $install_directory ]]; then + native_base_dir=$(GetNativeInstallDirectory) +fi + +install_bin="${native_base_dir}/bin" + +ReadGlobalJsonNativeTools + +if [[ ${#native_assets[@]} -eq 0 ]]; then + echo "No native tools defined in global.json" + exit 0; +else + native_installer_dir="$scriptroot/native" + for tool in "${!native_assets[@]}" + do + tool_version=${native_assets[$tool]} + installer_name="install-$tool.sh" + installer_command="$native_installer_dir/$installer_name" + installer_command+=" --baseuri $base_uri" + installer_command+=" --installpath $install_bin" + installer_command+=" --version $tool_version" + + if [[ $force = true ]]; then + installer_command+=" --force" + fi + + if [[ $clean = true ]]; then + installer_command+=" --clean" + fi + + $installer_command + + if [[ $? != 0 ]]; then + echo "Execution Failed" >&2 + exit 1 + fi + done +fi + +if [[ $clean = true ]]; then + exit 0 +fi + +if [[ -d $install_bin ]]; then + echo "Native tools are available from $install_bin" + echo "##vso[task.prependpath]$install_bin" +else + echo "Native tools install directory does not exist, installation failed" >&2 + exit 1 +fi + +exit 0 diff --git a/eng/common/internal/Directory.Build.props b/eng/common/internal/Directory.Build.props new file mode 100644 index 0000000000..e33179ef37 --- /dev/null +++ b/eng/common/internal/Directory.Build.props @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj new file mode 100644 index 0000000000..1a39a7ef3f --- /dev/null +++ b/eng/common/internal/Tools.csproj @@ -0,0 +1,27 @@ + + + + + net472 + false + + + + + + + + + + + https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + + $(RestoreSources); + https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; + + + + + + diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 new file mode 100644 index 0000000000..b37fd3d5e9 --- /dev/null +++ b/eng/common/msbuild.ps1 @@ -0,0 +1,27 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = "minimal", + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch] $ci, + [switch] $prepareMachine, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs +) + +. $PSScriptRoot\tools.ps1 + +try { + if ($ci) { + $nodeReuse = $false + } + + MSBuild @extraArgs +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} + +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh new file mode 100755 index 0000000000..8160cd5a59 --- /dev/null +++ b/eng/common/msbuild.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +verbosity='minimal' +warn_as_error=true +node_reuse=true +prepare_machine=false +extra_args='' + +while (($# > 0)); do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + --verbosity) + verbosity=$2 + shift 2 + ;; + --warnaserror) + warn_as_error=$2 + shift 2 + ;; + --nodereuse) + node_reuse=$2 + shift 2 + ;; + --ci) + ci=true + shift 1 + ;; + --preparemachine) + prepare_machine=true + shift 1 + ;; + *) + extra_args="$extra_args $1" + shift 1 + ;; + esac +done + +. "$scriptroot/tools.sh" + +if [[ "$ci" == true ]]; then + node_reuse=false +fi + +MSBuild $extra_args +ExitWithExitCode 0 diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 new file mode 100644 index 0000000000..f286ae0cde --- /dev/null +++ b/eng/common/native/CommonLibrary.psm1 @@ -0,0 +1,358 @@ +<# +.SYNOPSIS +Helper module to install an archive to a directory + +.DESCRIPTION +Helper module to download and extract an archive to a specified directory + +.PARAMETER Uri +Uri of artifact to download + +.PARAMETER InstallDirectory +Directory to extract artifact contents to + +.PARAMETER Force +Force download / extraction if file or contents already exist. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds. Default = 30 + +.NOTES +Returns False if download or extraction fail, True otherwise +#> +function DownloadAndExtract { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $InstallDirectory, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 + ) + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri + + # Download native tool + $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$Force ` + -Verbose:$Verbose + + if ($DownloadStatus -Eq $False) { + Write-Error "Download failed" + return $False + } + + # Extract native tool + $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$Force ` + -Verbose:$Verbose + + if ($UnzipStatus -Eq $False) { + Write-Error "Unzip failed" + return $False + } + return $True +} + +<# +.SYNOPSIS +Download a file, retry on failure + +.DESCRIPTION +Download specified file and retry if attempt fails + +.PARAMETER Uri +Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded + +.PARAMETER Path +Path to download or copy uri file to + +.PARAMETER Force +Overwrite existing file if present. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds Default = 30 + +#> +function Get-File { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $Path, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [switch] $Force = $False + ) + $Attempt = 0 + + if ($Force) { + if (Test-Path $Path) { + Remove-Item $Path -Force + } + } + if (Test-Path $Path) { + Write-Host "File '$Path' already exists, skipping download" + return $True + } + + $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent + if (-Not (Test-Path $DownloadDirectory)) { + New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null + } + + if (Test-Path -IsValid -Path $Uri) { + Write-Verbose "'$Uri' is a file path, copying file to '$Path'" + Copy-Item -Path $Uri -Destination $Path + return $? + } + else { + Write-Verbose "Downloading $Uri" + while($Attempt -Lt $DownloadRetries) + { + try { + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path + Write-Verbose "Downloaded to '$Path'" + return $True + } + catch { + $Attempt++ + if ($Attempt -Lt $DownloadRetries) { + $AttemptsLeft = $DownloadRetries - $Attempt + Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $RetryWaitTimeInSeconds + } + else { + Write-Error $_ + Write-Error $_.Exception + } + } + } + } + + return $False +} + +<# +.SYNOPSIS +Generate a shim for a native tool + +.DESCRIPTION +Creates a wrapper script (shim) that passes arguments forward to native tool assembly + +.PARAMETER ShimName +The name of the shim + +.PARAMETER ShimDirectory +The directory where shims are stored + +.PARAMETER ToolFilePath +Path to file that shim forwards to + +.PARAMETER Force +Replace shim if already present. Default = False + +.NOTES +Returns $True if generating shim succeeds, $False otherwise +#> +function New-ScriptShim { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ShimName, + [Parameter(Mandatory=$True)] + [string] $ShimDirectory, + [Parameter(Mandatory=$True)] + [string] $ToolFilePath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [switch] $Force + ) + try { + Write-Verbose "Generating '$ShimName' shim" + + if (-Not (Test-Path $ToolFilePath)){ + Write-Error "Specified tool file path '$ToolFilePath' does not exist" + return $False + } + + # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs + # Many of the checks for installed programs expect a .exe extension for Windows tools, rather + # than a .bat or .cmd file. + # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer + if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { + $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` + -InstallDirectory $ShimDirectory\WinShimmer ` + -Force:$Force ` + -DownloadRetries 2 ` + -RetryWaitTimeInSeconds 5 ` + -Verbose:$Verbose + } + + if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { + Write-Host "$ShimName.exe already exists; replacing..." + Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") + } + + Invoke-Expression "$ShimDirectory\WinShimmer\winshimmer.exe $ShimName $ToolFilePath $ShimDirectory" + return $True + } + catch { + Write-Host $_ + Write-Host $_.Exception + return $False + } +} + +<# +.SYNOPSIS +Returns the machine architecture of the host machine + +.NOTES +Returns 'x64' on 64 bit machines + Returns 'x86' on 32 bit machines +#> +function Get-MachineArchitecture { + $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE + $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 + if($ProcessorArchitecture -Eq "X86") + { + if(($ProcessorArchitectureW6432 -Eq "") -Or + ($ProcessorArchitectureW6432 -Eq "X86")) { + return "x86" + } + $ProcessorArchitecture = $ProcessorArchitectureW6432 + } + if (($ProcessorArchitecture -Eq "AMD64") -Or + ($ProcessorArchitecture -Eq "IA64") -Or + ($ProcessorArchitecture -Eq "ARM64")) { + return "x64" + } + return "x86" +} + +<# +.SYNOPSIS +Get the name of a temporary folder under the native install directory +#> +function Get-TempDirectory { + return Join-Path (Get-NativeInstallDirectory) "temp/" +} + +function Get-TempPathFilename { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Path + ) + $TempDir = CommonLibrary\Get-TempDirectory + $TempFilename = Split-Path $Path -leaf + $TempPath = Join-Path $TempDir $TempFilename + return $TempPath +} + +<# +.SYNOPSIS +Returns the base directory to use for native tool installation + +.NOTES +Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable +is set, or otherwise returns an install directory under the %USERPROFILE% +#> +function Get-NativeInstallDirectory { + $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY + if (!$InstallDir) { + $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" + } + return $InstallDir +} + +<# +.SYNOPSIS +Unzip an archive + +.DESCRIPTION +Powershell module to unzip an archive to a specified directory + +.PARAMETER ZipPath (Required) +Path to archive to unzip + +.PARAMETER OutputDirectory (Required) +Output directory for archive contents + +.PARAMETER Force +Overwrite output directory contents if they already exist + +.NOTES +- Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. +- Returns True if unzip operation is successful +- Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory +- Returns False if unable to extract zip archive +#> +function Expand-Zip { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ZipPath, + [Parameter(Mandatory=$True)] + [string] $OutputDirectory, + [switch] $Force + ) + + Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" + try { + if ((Test-Path $OutputDirectory) -And (-Not $Force)) { + Write-Host "Directory '$OutputDirectory' already exists, skipping extract" + return $True + } + if (Test-Path $OutputDirectory) { + Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" + Remove-Item $OutputDirectory -Force -Recurse + if ($? -Eq $False) { + Write-Error "Unable to remove '$OutputDirectory'" + return $False + } + } + if (-Not (Test-Path $OutputDirectory)) { + New-Item -path $OutputDirectory -Force -itemType "Directory" | Out-Null + } + + Add-Type -assembly "system.io.compression.filesystem" + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$OutputDirectory") + if ($? -Eq $False) { + Write-Error "Unable to extract '$ZipPath'" + return $False + } + } + catch { + Write-Host $_ + Write-Host $_.Exception + + return $False + } + return $True +} + +export-modulemember -function DownloadAndExtract +export-modulemember -function Expand-Zip +export-modulemember -function Get-File +export-modulemember -function Get-MachineArchitecture +export-modulemember -function Get-NativeInstallDirectory +export-modulemember -function Get-TempDirectory +export-modulemember -function Get-TempPathFilename +export-modulemember -function New-ScriptShim diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh new file mode 100755 index 0000000000..271bddfac5 --- /dev/null +++ b/eng/common/native/common-library.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +function GetNativeInstallDirectory { + local install_dir + + if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then + install_dir=$HOME/.netcoreeng/native/ + else + install_dir=$NETCOREENG_INSTALL_DIRECTORY + fi + + echo $install_dir + return 0 +} + +function GetTempDirectory { + + echo $(GetNativeInstallDirectory)temp/ + return 0 +} + +function ExpandZip { + local zip_path=$1 + local output_directory=$2 + local force=${3:-false} + + echo "Extracting $zip_path to $output_directory" + if [[ -d $output_directory ]] && [[ $force = false ]]; then + echo "Directory '$output_directory' already exists, skipping extract" + return 0 + fi + + if [[ -d $output_directory ]]; then + echo "'Force flag enabled, but '$output_directory' exists. Removing directory" + rm -rf $output_directory + if [[ $? != 0 ]]; then + echo Unable to remove '$output_directory'>&2 + return 1 + fi + fi + + echo "Creating directory: '$output_directory'" + mkdir -p $output_directory + + echo "Extracting archive" + tar -xf $zip_path -C $output_directory + if [[ $? != 0 ]]; then + echo "Unable to extract '$zip_path'" >&2 + return 1 + fi + + return 0 +} + +function GetCurrentOS { + local unameOut="$(uname -s)" + case $unameOut in + Linux*) echo "Linux";; + Darwin*) echo "MacOS";; + esac + return 0 +} + +function GetFile { + local uri=$1 + local path=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + if [[ -f $path ]]; then + if [[ $force = false ]]; then + echo "File '$path' already exists. Skipping download" + return 0 + else + rm -rf $path + fi + fi + + if [[ -f $uri ]]; then + echo "'$uri' is a file path, copying file to '$path'" + cp $uri $path + return $? + fi + + echo "Downloading $uri" + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail + else + wget -q -O "$path" "$uri" --tries="$download_retries" + fi + + return $? +} + +function GetTempPathFileName { + local path=$1 + + local temp_dir=$(GetTempDirectory) + local temp_file_name=$(basename $path) + echo $temp_dir$temp_file_name + return 0 +} + +function DownloadAndExtract { + local uri=$1 + local installDir=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + local temp_tool_path=$(GetTempPathFileName $uri) + + echo "downloading to: $temp_tool_path" + + # Download file + GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + echo "Failed to download '$uri' to '$temp_tool_path'." >&2 + return 1 + fi + + # Extract File + echo "extracting from $temp_tool_path to $installDir" + ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + echo "Failed to extract '$temp_tool_path' to '$installDir'." >&2 + return 1 + fi + + return 0 +} + +function NewScriptShim { + local shimpath=$1 + local tool_file_path=$2 + local force=${3:-false} + + echo "Generating '$shimpath' shim" + if [[ -f $shimpath ]]; then + if [[ $force = false ]]; then + echo "File '$shimpath' already exists." >&2 + return 1 + else + rm -rf $shimpath + fi + fi + + if [[ ! -f $tool_file_path ]]; then + echo "Specified tool file path:'$tool_file_path' does not exist" >&2 + return 1 + fi + + local shim_contents=$'#!/usr/bin/env bash\n' + shim_contents+="SHIMARGS="$'$1\n' + shim_contents+="$tool_file_path"$' $SHIMARGS\n' + + # Write shim file + echo "$shim_contents" > $shimpath + + chmod +x $shimpath + + echo "Finished generating shim '$shimpath'" + + return $? +} + diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh new file mode 100755 index 0000000000..293af6017d --- /dev/null +++ b/eng/common/native/install-cmake.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake" +tool_os=$(GetCurrentOS) +tool_folder=$(echo $tool_os | awk '{print tolower($0)}') +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/cmake/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + echo "Installation failed" >&2 + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + echo "Shim generation failed" >&2 + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 new file mode 100644 index 0000000000..635ab3fd41 --- /dev/null +++ b/eng/common/native/install-tool.ps1 @@ -0,0 +1,130 @@ +<# +.SYNOPSIS +Install native tool + +.DESCRIPTION +Install cmake native tool from Azure blob storage + +.PARAMETER InstallPath +Base directory to install native tool to + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER CommonLibraryDirectory +Path to folder containing common library modules + +.PARAMETER Force +Force install of tools even if they previously exist + +.PARAMETER Clean +Don't install the tool, just clean up the current install of the tool + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.NOTES +Returns 0 if install succeeds, 1 otherwise +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [Parameter(Mandatory=$True)] + [string] $ToolName, + [Parameter(Mandatory=$True)] + [string] $InstallPath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [Parameter(Mandatory=$True)] + [string] $Version, + [string] $CommonLibraryDirectory = $PSScriptRoot, + [switch] $Force = $False, + [switch] $Clean = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 +) + +# Import common library modules +Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $Arch = CommonLibrary\Get-MachineArchitecture + $ToolOs = "win64" + if($Arch -Eq "x32") { + $ToolOs = "win32" + } + $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" + $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" + $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" + $ShimPath = Join-Path $InstallPath "$ToolName.exe" + + if ($Clean) { + Write-Host "Cleaning $ToolInstallDirectory" + if (Test-Path $ToolInstallDirectory) { + Remove-Item $ToolInstallDirectory -Force -Recurse + } + Write-Host "Cleaning $ShimPath" + if (Test-Path $ShimPath) { + Remove-Item $ShimPath -Force + } + $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri + Write-Host "Cleaning $ToolTempPath" + if (Test-Path $ToolTempPath) { + Remove-Item $ToolTempPath -Force + } + exit 0 + } + + # Install tool + if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { + Write-Verbose "$ToolName ($Version) already exists, skipping install" + } + else { + $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` + -InstallDirectory $ToolInstallDirectory ` + -Force:$Force ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Verbose:$Verbose + + if ($InstallStatus -Eq $False) { + Write-Error "Installation failed" + exit 1 + } + } + + $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } + if (@($ToolFilePath).Length -Gt 1) { + Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" + exit 1 + } elseif (@($ToolFilePath).Length -Lt 1) { + Write-Error "$ToolName was not found in $ToolFilePath." + exit 1 + } + + # Generate shim + # Always rewrite shims so that we are referencing the expected version + $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` + -ShimDirectory $InstallPath ` + -ToolFilePath "$ToolFilePath" ` + -BaseUri $BaseUri ` + -Force:$Force ` + -Verbose:$Verbose + + if ($GenerateShimStatus -Eq $False) { + Write-Error "Generate shim failed" + return 1 + } + + exit 0 +} +catch { + Write-Host $_ + Write-Host $_.Exception + exit 1 +} diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 new file mode 100644 index 0000000000..d0eec5163e --- /dev/null +++ b/eng/common/sdk-task.ps1 @@ -0,0 +1,79 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $configuration = "Debug", + [string] $task, + [string] $verbosity = "minimal", + [string] $msbuildEngine = $null, + [switch] $restore, + [switch] $prepareMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +$ci = $true +$binaryLog = $true +$warnAsError = $true + +. $PSScriptRoot\tools.ps1 + +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + Write-Host " -restore Restore dependencies" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -prepareMachine Prepare machine for CI run" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + Write-Host "Command line arguments not listed above are passed thru to msbuild." +} + +function Build([string]$target) { + $logSuffix = if ($target -eq "Execute") { "" } else { ".$target" } + $log = Join-Path $LogDir "$task$logSuffix.binlog" + $outputPath = Join-Path $ToolsetDir "$task\\" + + MSBuild $taskProject ` + /bl:$log ` + /t:$target ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:BaseIntermediateOutputPath=$outputPath ` + @properties +} + +try { + if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { + Print-Usage + exit 0 + } + + if ($task -eq "") { + Write-Host "Missing required parameter '-task '" -ForegroundColor Red + Print-Usage + ExitWithExitCode 1 + } + + $taskProject = GetSdkTaskProject $task + if (!(Test-Path $taskProject)) { + Write-Host "Unknown task: $task" -ForegroundColor Red + ExitWithExitCode 1 + } + + if ($restore) { + Build "Restore" + } + + Build "Execute" +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/templates/job/generate-graph-files.yml b/eng/common/templates/job/generate-graph-files.yml new file mode 100644 index 0000000000..e54ce956f9 --- /dev/null +++ b/eng/common/templates/job/generate-graph-files.yml @@ -0,0 +1,48 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + +jobs: +- job: Generate_Graph_Files + + dependsOn: ${{ parameters.dependsOn }} + + displayName: Generate Graph Files + + pool: ${{ parameters.pool }} + + variables: + # Publish-Build-Assets provides: MaestroAccessToken, BotAccount-dotnet-maestro-bot-PAT + # DotNet-AllOrgs-Darc-Pats provides: dn-bot-devdiv-dnceng-rw-code-pat + - group: Publish-Build-Assets + - group: DotNet-AllOrgs-Darc-Pats + - name: _GraphArguments + value: -gitHubPat $(BotAccount-dotnet-maestro-bot-PAT) + -azdoPat $(dn-bot-devdiv-dnceng-rw-code-pat) + -barToken $(MaestroAccessToken) + -outputFolder '$(Build.StagingDirectory)/GraphFiles/' + - ${{ if ne(parameters.includeToolset, 'false') }}: + - name: _GraphArguments + value: ${{ variables._GraphArguments }} -includeToolset + + steps: + - task: PowerShell@2 + displayName: Generate Graph Files + inputs: + filePath: eng\common\generate-graph-files.ps1 + arguments: $(_GraphArguments) + continueOnError: true + - task: PublishBuildArtifacts@1 + displayName: Publish Graph to Artifacts + inputs: + PathtoPublish: '$(Build.StagingDirectory)/GraphFiles' + PublishLocation: Container + ArtifactName: GraphFiles + continueOnError: true + condition: always() diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml new file mode 100644 index 0000000000..1814e0ab61 --- /dev/null +++ b/eng/common/templates/job/job.yml @@ -0,0 +1,205 @@ +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + + condition: '' + + continueOnError: false + + container: '' + + dependsOn: '' + + displayName: '' + + steps: [] + + pool: '' + + strategy: '' + + timeoutInMinutes: '' + + variables: [] + + workspace: '' + +# Job base template specific parameters + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' + enableMicrobuild: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing to the build asset registry + enablePublishBuildAssets: false + + # Optional: Include PublishTestResults task + enablePublishTestResults: false + + # Optional: enable sending telemetry + enableTelemetry: false + + # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') + helixRepo: '' + + # Optional: define the helix type for telemetry (example: 'build/product/') + helixType: '' + + # Required: name of the job + name: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + variables: + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions + - task: sendStartTelemetry@0 + displayName: 'Send Helix Start Telemetry' + inputs: + helixRepo: ${{ parameters.helixRepo }} + ${{ if ne(parameters.helixType, '') }}: + helixType: ${{ parameters.helixType }} + buildConfig: $(_BuildConfig) + runAsPublic: ${{ parameters.runAsPublic }} + continueOnError: ${{ parameters.continueOnError }} + condition: always() + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildSigningPlugin@2 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions + - task: sendEndTelemetry@0 + displayName: 'Send Helix End Telemetry' + continueOnError: ${{ parameters.continueOnError }} + condition: always() + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: $(Agent.Os)_$(Agent.JobName) + continueOnError: true + condition: always() + + - ${{ if eq(parameters.enablePublishTestResults, 'true') }}: + - task: PublishTestResults@2 + displayName: Publish Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.enablePublishBuildAssets, true), ne(variables['_PublishUsingPipelines'], 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.StagingDirectory)/AssetManifests' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.StagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml new file mode 100644 index 0000000000..620bd3c62e --- /dev/null +++ b/eng/common/templates/job/publish-build-assets.yml @@ -0,0 +1,70 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + + displayName: Publish to Build Asset Registry + + pool: ${{ parameters.pool }} + + variables: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - name: _BuildConfig + value: ${{ parameters.configuration }} + - group: Publish-Build-Assets + + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:Configuration=$(_BuildConfig) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - task: PublishBuildArtifacts@1 + displayName: Publish Logs to VSTS + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: $(Agent.Os)_PublishBuildAssets + continueOnError: true + condition: always() diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml new file mode 100644 index 0000000000..6a2f98c036 --- /dev/null +++ b/eng/common/templates/jobs/jobs.yml @@ -0,0 +1,90 @@ +parameters: + # Optional: 'true' if failures in job.yml job should not fail the job + continueOnError: false + + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' + enableMicrobuild: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing to the build asset registry + enablePublishBuildAssets: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Optional: Include PublishTestResults task + enablePublishTestResults: false + + # Optional: enable sending telemetry + # if enabled then the 'helixRepo' parameter should also be specified + enableTelemetry: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') + helixRepo: '' + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if and(eq(parameters.enablePublishBuildAssets, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + pool: + vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + +- ${{ if and(eq(parameters.graphFileGeneration.enabled, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: ../job/generate-graph-files.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} + dependsOn: + - Asset_Registry_Publish + pool: + vmImage: vs2017-win2016 diff --git a/eng/common/templates/phases/base.yml b/eng/common/templates/phases/base.yml new file mode 100644 index 0000000000..0123cf43b1 --- /dev/null +++ b/eng/common/templates/phases/base.yml @@ -0,0 +1,130 @@ +parameters: + # Optional: Clean sources before building + clean: true + + # Optional: Git fetch depth + fetchDepth: '' + + # Optional: name of the phase (not specifying phase name may cause name collisions) + name: '' + # Optional: display name of the phase + displayName: '' + + # Optional: condition for the job to run + condition: '' + + # Optional: dependencies of the phase + dependsOn: '' + + # Required: A defined YAML queue + queue: {} + + # Required: build steps + steps: [] + + # Optional: variables + variables: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + ## Telemetry variables + + # Optional: enable sending telemetry + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _HelixBuildConfig - differentiate between Debug, Release, other + # _HelixSource - Example: build/product + # _HelixType - Example: official/dotnet/arcade/$(Build.SourceBranch) + enableTelemetry: false + + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' + enableMicrobuild: false + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +phases: +- phase: ${{ parameters.name }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + queue: ${{ parameters.queue }} + + ${{ if ne(parameters.variables, '') }}: + variables: + ${{ insert }}: ${{ parameters.variables }} + + steps: + - checkout: self + clean: ${{ parameters.clean }} + ${{ if ne(parameters.fetchDepth, '') }}: + fetchDepth: ${{ parameters.fetchDepth }} + + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - template: /eng/common/templates/steps/telemetry-start.yml + parameters: + buildConfig: $(_HelixBuildConfig) + helixSource: $(_HelixSource) + helixType: $(_HelixType) + runAsPublic: ${{ parameters.runAsPublic }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + # Internal only resource, and Microbuild signing shouldn't be applied to PRs. + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildSigningPlugin@2 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + + env: + TeamName: $(_TeamName) + continueOnError: false + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + # Run provided build steps + - ${{ parameters.steps }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + # Internal only resources + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + env: + TeamName: $(_TeamName) + + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - template: /eng/common/templates/steps/telemetry-end.yml + parameters: + helixSource: $(_HelixSource) + helixType: $(_HelixType) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.StagingDirectory)/AssetManifests' + continueOnError: false + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.StagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: false + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) diff --git a/eng/common/templates/phases/publish-build-assets.yml b/eng/common/templates/phases/publish-build-assets.yml new file mode 100644 index 0000000000..a0a8074282 --- /dev/null +++ b/eng/common/templates/phases/publish-build-assets.yml @@ -0,0 +1,51 @@ +parameters: + dependsOn: '' + queue: {} + configuration: 'Debug' + condition: succeeded() + continueOnError: false + runAsPublic: false + publishUsingPipelines: false +phases: + - phase: Asset_Registry_Publish + displayName: Publish to Build Asset Registry + dependsOn: ${{ parameters.dependsOn }} + queue: ${{ parameters.queue }} + variables: + _BuildConfig: ${{ parameters.configuration }} + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'DotNet-Engineering-Services_KeyVault' + KeyVaultName: EngKeyVault + SecretsFilter: 'MaestroAccessToken' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:Configuration=$(_BuildConfig) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: PublishBuildArtifacts@1 + displayName: Publish Logs to VSTS + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: $(Agent.Os)_Asset_Registry_Publish + continueOnError: true + condition: always() diff --git a/eng/common/templates/steps/build-reason.yml b/eng/common/templates/steps/build-reason.yml new file mode 100644 index 0000000000..eba58109b5 --- /dev/null +++ b/eng/common/templates/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-on-unix.yml b/eng/common/templates/steps/run-on-unix.yml new file mode 100644 index 0000000000..e1733814f6 --- /dev/null +++ b/eng/common/templates/steps/run-on-unix.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if ne(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-on-windows.yml b/eng/common/templates/steps/run-on-windows.yml new file mode 100644 index 0000000000..73e7e9c275 --- /dev/null +++ b/eng/common/templates/steps/run-on-windows.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if eq(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-script-ifequalelse.yml b/eng/common/templates/steps/run-script-ifequalelse.yml new file mode 100644 index 0000000000..3d1242f558 --- /dev/null +++ b/eng/common/templates/steps/run-script-ifequalelse.yml @@ -0,0 +1,33 @@ +parameters: + # if parameter1 equals parameter 2, run 'ifScript' command, else run 'elsescript' command + parameter1: '' + parameter2: '' + ifScript: '' + elseScript: '' + + # name of script step + name: Script + + # display name of script step + displayName: If-Equal-Else Script + + # environment + env: {} + + # conditional expression for step execution + condition: '' + +steps: +- ${{ if and(ne(parameters.ifScript, ''), eq(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.ifScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} + +- ${{ if and(ne(parameters.elseScript, ''), ne(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.elseScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml new file mode 100644 index 0000000000..d1ce577db5 --- /dev/null +++ b/eng/common/templates/steps/send-to-helix.yml @@ -0,0 +1,88 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in seconds for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json + EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/steps/telemetry-end.yml b/eng/common/templates/steps/telemetry-end.yml new file mode 100644 index 0000000000..fadc04ca1b --- /dev/null +++ b/eng/common/templates/steps/telemetry-end.yml @@ -0,0 +1,102 @@ +parameters: + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- bash: | + if [ "$AGENT_JOBSTATUS" = "Succeeded" ] || [ "$AGENT_JOBSTATUS" = "PartiallySucceeded" ]; then + errorCount=0 + else + errorCount=1 + fi + warningCount=0 + + curlStatus=1 + retryCount=0 + # retry loop to harden against spotty telemetry connections + # we don't retry successes and 4xx client errors + until [[ $curlStatus -eq 0 || ( $curlStatus -ge 400 && $curlStatus -le 499 ) || $retryCount -ge $MaxRetries ]] + do + if [ $retryCount -gt 0 ]; then + echo "Failed to send telemetry to Helix; waiting $RetryDelay seconds before retrying..." + sleep $RetryDelay + fi + + # create a temporary file for curl output + res=`mktemp` + + curlResult=` + curl --verbose --output $res --write-out "%{http_code}"\ + -H 'Content-Type: application/json' \ + -H "X-Helix-Job-Token: $Helix_JobToken" \ + -H 'Content-Length: 0' \ + -X POST -G "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$Helix_WorkItemId/finish" \ + --data-urlencode "errorCount=$errorCount" \ + --data-urlencode "warningCount=$warningCount"` + curlStatus=$? + + if [ $curlStatus -eq 0 ]; then + if [ $curlResult -gt 299 ] || [ $curlResult -lt 200 ]; then + curlStatus=$curlResult + fi + fi + + let retryCount++ + done + + if [ $curlStatus -ne 0 ]; then + echo "Failed to Send Build Finish information after $retryCount retries" + vstsLogOutput="vso[task.logissue type=error;sourcepath=templates/steps/telemetry-end.yml;code=1;]Failed to Send Build Finish information: $curlStatus" + echo "##$vstsLogOutput" + exit 1 + fi + displayName: Send Unix Build End Telemetry + env: + # defined via VSTS variables in start-job.sh + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(), ne(variables['Agent.Os'], 'Windows_NT')) +- powershell: | + if (($env:Agent_JobStatus -eq 'Succeeded') -or ($env:Agent_JobStatus -eq 'PartiallySucceeded')) { + $ErrorCount = 0 + } else { + $ErrorCount = 1 + } + $WarningCount = 0 + + # Basic retry loop to harden against server flakiness + $retryCount = 0 + while ($retryCount -lt $env:MaxRetries) { + try { + Invoke-RestMethod -Uri "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$env:Helix_WorkItemId/finish?errorCount=$ErrorCount&warningCount=$WarningCount" -Method Post -ContentType "application/json" -Body "" ` + -Headers @{ 'X-Helix-Job-Token'=$env:Helix_JobToken } + break + } + catch { + $statusCode = $_.Exception.Response.StatusCode.value__ + if ($statusCode -ge 400 -and $statusCode -le 499) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix (status code $statusCode); not retrying (4xx client error)" + Write-Host "##vso[task.logissue]error ", $_.Exception.GetType().FullName, $_.Exception.Message + exit 1 + } + Write-Host "Failed to send telemetry to Helix (status code $statusCode); waiting $env:RetryDelay seconds before retrying..." + $retryCount++ + sleep $env:RetryDelay + continue + } + } + + if ($retryCount -ge $env:MaxRetries) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix after $retryCount retries." + exit 1 + } + displayName: Send Windows Build End Telemetry + env: + # defined via VSTS variables in start-job.ps1 + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(),eq(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/templates/steps/telemetry-start.yml b/eng/common/templates/steps/telemetry-start.yml new file mode 100644 index 0000000000..32c01ef0b5 --- /dev/null +++ b/eng/common/templates/steps/telemetry-start.yml @@ -0,0 +1,241 @@ +parameters: + helixSource: 'undefined_defaulted_in_telemetry.yml' + helixType: 'undefined_defaulted_in_telemetry.yml' + buildConfig: '' + runAsPublic: false + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- ${{ if and(eq(parameters.runAsPublic, 'false'), not(eq(variables['System.TeamProject'], 'public'))) }}: + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'HelixProd_KeyVault' + KeyVaultName: HelixProdKV + SecretsFilter: 'HelixApiAccessToken' + condition: always() +- bash: | + # create a temporary file + jobInfo=`mktemp` + + # write job info content to temporary file + cat > $jobInfo <' | Set-Content $proj + + MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile + + $path = Get-Content $toolsetLocationFile -TotalCount 1 + if (!(Test-Path $path)) { + throw "Invalid toolset path: $path" + } + + return $global:_ToolsetBuildProj = $path +} + +function ExitWithExitCode([int] $exitCode) { + if ($ci -and $prepareMachine) { + Stop-Processes + } + exit $exitCode +} + +function Stop-Processes() { + Write-Host "Killing running build processes..." + foreach ($processName in $processesToStopOnExit) { + Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process + } +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild() { + if ($pipelinesLog) { + $buildTool = InitializeBuildTool + $toolsetBuildProject = InitializeToolset + $path = Split-Path -parent $toolsetBuildProject + $path = Join-Path $path (Join-Path $buildTool.Framework "Microsoft.DotNet.Arcade.Sdk.dll") + $args += "/logger:$path" + } + + MSBuild-Core @args +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild-Core() { + if ($ci) { + if (!$binaryLog) { + throw "Binary log must be enabled in CI build." + } + + if ($nodeReuse) { + throw "Node reuse must be disabled in CI build." + } + } + + $buildTool = InitializeBuildTool + + $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" + + if ($warnAsError) { + $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" + } + + foreach ($arg in $args) { + if ($arg -ne $null -and $arg.Trim() -ne "") { + $cmdArgs += " `"$arg`"" + } + } + + $exitCode = Exec-Process $buildTool.Path $cmdArgs + + if ($exitCode -ne 0) { + Write-Host "Build failed." -ForegroundColor Red + + $buildLog = GetMSBuildBinaryLogCommandLineArgument $args + if ($buildLog -ne $null) { + Write-Host "See log: $buildLog" -ForegroundColor DarkGray + } + + ExitWithExitCode $exitCode + } +} + +function GetMSBuildBinaryLogCommandLineArgument($arguments) { + foreach ($argument in $arguments) { + if ($argument -ne $null) { + $arg = $argument.Trim() + if ($arg.StartsWith("/bl:", "OrdinalIgnoreCase")) { + return $arg.Substring("/bl:".Length) + } + + if ($arg.StartsWith("/binaryLogger:", "OrdinalIgnoreCase")) { + return $arg.Substring("/binaryLogger:".Length) + } + } + } + + return $null +} + +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$ArtifactsDir = Join-Path $RepoRoot "artifacts" +$ToolsetDir = Join-Path $ArtifactsDir "toolset" +$ToolsDir = Join-Path $RepoRoot ".tools" +$LogDir = Join-Path (Join-Path $ArtifactsDir "log") $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir "tmp") $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot "global.json") | ConvertFrom-Json +# true if global.json contains a "runtimes" section +$globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } + +Create-Directory $ToolsetDir +Create-Directory $TempDir +Create-Directory $LogDir + +if ($ci) { + Write-Host "##vso[task.setvariable variable=Artifacts]$ArtifactsDir" + Write-Host "##vso[task.setvariable variable=Artifacts.Toolset]$ToolsetDir" + Write-Host "##vso[task.setvariable variable=Artifacts.Log]$LogDir" + + $env:TEMP = $TempDir + $env:TMP = $TempDir +} diff --git a/eng/common/tools.sh b/eng/common/tools.sh new file mode 100755 index 0000000000..df3eb8bce0 --- /dev/null +++ b/eng/common/tools.sh @@ -0,0 +1,365 @@ +# Initialize variables if they aren't already defined. + +# CI mode - set to true on CI server for PR validation build or official build. +ci=${ci:-false} + +# Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. +configuration=${configuration:-'Debug'} + +# Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. +# Binary log must be enabled on CI. +binary_log=${binary_log:-$ci} + +# Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). +prepare_machine=${prepare_machine:-false} + +# True to restore toolsets and dependencies. +restore=${restore:-true} + +# Adjusts msbuild verbosity level. +verbosity=${verbosity:-'minimal'} + +# Set to true to reuse msbuild nodes. Recommended to not reuse on CI. +if [[ "$ci" == true ]]; then + node_reuse=${node_reuse:-false} +else + node_reuse=${node_reuse:-true} +fi + +# Configures warning treatment in msbuild. +warn_as_error=${warn_as_error:-true} + +# True to attempt using .NET Core already that meets requirements specified in global.json +# installed on the machine instead of downloading one. +use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} + +# True to use global NuGet cache instead of restoring packages to repository-local directory. +if [[ "$ci" == true ]]; then + use_global_nuget_cache=${use_global_nuget_cache:-false} +else + use_global_nuget_cache=${use_global_nuget_cache:-true} +fi + +# Resolve any symlinks in the given path. +function ResolvePath { + local path=$1 + + while [[ -h $path ]]; do + local dir="$( cd -P "$( dirname "$path" )" && pwd )" + path="$(readlink "$path")" + + # if $path was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $path != /* ]] && path="$dir/$path" + done + + # return value + _ResolvePath="$path" +} + +# ReadVersionFromJson [json key] +function ReadGlobalVersion { + local key=$1 + + local line=`grep -m 1 "$key" "$global_json_file"` + local pattern="\"$key\" *: *\"(.*)\"" + + if [[ ! $line =~ $pattern ]]; then + echo "Error: Cannot find \"$key\" in $global_json_file" >&2 + ExitWithExitCode 1 + fi + + # return value + _ReadGlobalVersion=${BASH_REMATCH[1]} +} + +function InitializeDotNetCli { + if [[ -n "${_InitializeDotNetCli:-}" ]]; then + return + fi + + local install=$1 + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + export DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we want to control all package sources + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI + if [[ $ci == true ]]; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + fi + + # LTTNG is the logging infrastructure used by Core CLR. Need this variable set + # so it doesn't output warnings to the console. + export LTTNG_HOME="$HOME" + + # Source Build uses DotNetCoreSdkDir variable + if [[ -n "${DotNetCoreSdkDir:-}" ]]; then + export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir" + fi + + # Find the first path on $PATH that contains the dotnet.exe + if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then + local dotnet_path=`command -v dotnet` + if [[ -n "$dotnet_path" ]]; then + ResolvePath "$dotnet_path" + export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` + fi + fi + + ReadGlobalVersion "dotnet" + local dotnet_sdk_version=$_ReadGlobalVersion + local dotnet_root="" + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + dotnet_root="$DOTNET_INSTALL_DIR" + else + dotnet_root="$repo_root/.dotnet" + + export DOTNET_INSTALL_DIR="$dotnet_root" + + if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + if [[ "$install" == true ]]; then + InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" + else + echo "Unable to find dotnet with SDK version '$dotnet_sdk_version'" >&2 + ExitWithExitCode 1 + fi + fi + fi + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + export PATH="$dotnet_root:$PATH" + + if [[ $ci == true ]]; then + # Make Sure that our bootstrapped dotnet cli is avaliable in future steps of the Azure Pipelines build + echo "##vso[task.prependpath]$dotnet_root" + echo "##vso[task.setvariable variable=DOTNET_MULTILEVEL_LOOKUP]0" + echo "##vso[task.setvariable variable=DOTNET_SKIP_FIRST_TIME_EXPERIENCE]1" + fi + + # return value + _InitializeDotNetCli="$dotnet_root" +} + +function InstallDotNetSdk { + local root=$1 + local version=$2 + local architecture="" + if [[ $# == 3 ]]; then + architecture=$3 + fi + InstallDotNet "$root" "$version" $architecture +} + +function InstallDotNet { + local root=$1 + local version=$2 + + GetDotNetInstallScript "$root" + local install_script=$_GetDotNetInstallScript + + local archArg='' + if [[ -n "${3:-}" ]]; then + archArg="--architecture $3" + fi + local runtimeArg='' + if [[ -n "${4:-}" ]]; then + runtimeArg="--runtime $4" + fi + + local skipNonVersionedFilesArg="" + if [[ "$#" -ge "5" ]]; then + skipNonVersionedFilesArg="--skip-non-versioned-files" + fi + bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg || { + local exit_code=$? + echo "Failed to install dotnet SDK (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code + } +} + +function GetDotNetInstallScript { + local root=$1 + local install_script="$root/dotnet-install.sh" + local install_script_url="https://dot.net/v1/dotnet-install.sh" + + if [[ ! -a "$install_script" ]]; then + mkdir -p "$root" + + echo "Downloading '$install_script_url'" + + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" + else + wget -q -O "$install_script" "$install_script_url" + fi + fi + + # return value + _GetDotNetInstallScript="$install_script" +} + +function InitializeBuildTool { + if [[ -n "${_InitializeBuildTool:-}" ]]; then + return + fi + + InitializeDotNetCli $restore + + # return values + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildToolCommand="msbuild" +} + +function GetNuGetPackageCachePath { + if [[ -z ${NUGET_PACKAGES:-} ]]; then + if [[ "$use_global_nuget_cache" == true ]]; then + export NUGET_PACKAGES="$HOME/.nuget/packages" + else + export NUGET_PACKAGES="$repo_root/.packages" + fi + fi + + # return value + _GetNuGetPackageCachePath=$NUGET_PACKAGES +} + +function InitializeNativeTools() { + if grep -Fq "native-tools" $global_json_file + then + local nativeArgs="" + if [[ "$ci" == true ]]; then + nativeArgs="-InstallDirectory $tools_dir" + fi + "$_script_dir/init-tools-native.sh" $nativeArgs + fi +} + +function InitializeToolset { + if [[ -n "${_InitializeToolset:-}" ]]; then + return + fi + + GetNuGetPackageCachePath + + ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" + + local toolset_version=$_ReadGlobalVersion + local toolset_location_file="$toolset_dir/$toolset_version.txt" + + if [[ -a "$toolset_location_file" ]]; then + local path=`cat "$toolset_location_file"` + if [[ -a "$path" ]]; then + # return value + _InitializeToolset="$path" + return + fi + fi + + if [[ "$restore" != true ]]; then + echo "Toolset version $toolsetVersion has not been restored." >&2 + ExitWithExitCode 2 + fi + + local proj="$toolset_dir/restore.proj" + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:$log_dir/ToolsetRestore.binlog" + fi + + echo '' > "$proj" + MSBuild "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" + + local toolset_build_proj=`cat "$toolset_location_file"` + + if [[ ! -a "$toolset_build_proj" ]]; then + echo "Invalid toolset path: $toolset_build_proj" >&2 + ExitWithExitCode 3 + fi + + # return value + _InitializeToolset="$toolset_build_proj" +} + +function ExitWithExitCode { + if [[ "$ci" == true && "$prepare_machine" == true ]]; then + StopProcesses + fi + exit $1 +} + +function StopProcesses { + echo "Killing running build processes..." + pkill -9 "dotnet" || true + pkill -9 "vbcscompiler" || true + return 0 +} + +function MSBuild { + if [[ "$ci" == true ]]; then + if [[ "$binary_log" != true ]]; then + echo "Binary log must be enabled in CI build." >&2 + ExitWithExitCode 1 + fi + + if [[ "$node_reuse" == true ]]; then + echo "Node reuse must be disabled in CI build." >&2 + ExitWithExitCode 1 + fi + fi + + InitializeBuildTool + + local warnaserror_switch="" + if [[ $warn_as_error == true ]]; then + warnaserror_switch="/warnaserror" + fi + + "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || { + local exit_code=$? + echo "Build failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code + } +} + +ResolvePath "${BASH_SOURCE[0]}" +_script_dir=`dirname "$_ResolvePath"` + +eng_root=`cd -P "$_script_dir/.." && pwd` +repo_root=`cd -P "$_script_dir/../.." && pwd` +artifacts_dir="$repo_root/artifacts" +toolset_dir="$artifacts_dir/toolset" +tools_dir="$repo_root/.tools" +log_dir="$artifacts_dir/log/$configuration" +temp_dir="$artifacts_dir/tmp/$configuration" + +global_json_file="$repo_root/global.json" +# determine if global.json contains a "runtimes" entry +global_json_has_runtimes=false +dotnetlocal_key=`grep -m 1 "runtimes" "$global_json_file"` || true +if [[ -n "$dotnetlocal_key" ]]; then + global_json_has_runtimes=true +fi + +# HOME may not be defined in some scenarios, but it is required by NuGet +if [[ -z $HOME ]]; then + export HOME="$repo_root/artifacts/.home/" + mkdir -p "$HOME" +fi + +mkdir -p "$toolset_dir" +mkdir -p "$temp_dir" +mkdir -p "$log_dir" + +if [[ $ci == true ]]; then + export TEMP="$temp_dir" + export TMP="$temp_dir" +fi \ No newline at end of file From 0f9628908750cc190f8b14c91e47e6dfe22baa93 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 20 May 2019 14:06:28 -0700 Subject: [PATCH 21/41] Update CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b962971e33..ed445df453 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,6 +6,7 @@ /.config/ @aspnet/build /build/ @aspnet/build /eng/ @aspnet/build +/eng/common/ @dotnet-maestro-bot /eng/Versions.props @dotnet-maestro-bot @dougbu /eng/Version.Details.xml @dotnet-maestro-bot @dougbu /src/Components/ @SteveSandersonMS From 16a47948f80fede807fabe3c291d793590e8fd17 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 20 May 2019 16:20:19 -0700 Subject: [PATCH 22/41] Move AuthZ policy types back into Policy and rejigger AddAuthorization (#10021) --- eng/ProjectReferences.props | 1 + eng/SharedFramework.Local.props | 1 + ...NetCore.Http.Abstractions.netcoreapp3.0.cs | 12 -- .../ref/Microsoft.AspNetCore.Metadata.csproj | 10 ++ ...soft.AspNetCore.Metadata.netstandard2.0.cs | 15 +++ .../src/IAllowAnonymous.cs | 0 .../src/IAuthorizeData.cs | 0 .../src/Microsoft.AspNetCore.Metadata.csproj | 12 ++ .../ref/Microsoft.AspNetCore.Routing.csproj | 1 + .../src/Microsoft.AspNetCore.Routing.csproj | 1 + .../src/Properties/AssemblyInfo.cs | 1 - .../Microsoft.AspNetCore.Authorization.csproj | 10 +- ...spNetCore.Authorization.netstandard2.0.cs} | 49 +------ .../Core/src/AuthorizationOptions.cs | 4 +- ...uthorizationServiceCollectionExtensions.cs | 17 +-- .../Microsoft.AspNetCore.Authorization.csproj | 12 +- .../Core/src/Properties/AssemblyInfo.cs | 3 +- .../Core/src/Properties/Resources.Designer.cs | 14 -- .../Authorization/Core/src/Resources.resx | 3 - ...oft.AspNetCore.Authorization.Policy.csproj | 3 + ...Core.Authorization.Policy.netcoreapp3.0.cs | 51 +++++++- .../src}/AuthorizationAppBuilderExtensions.cs | 4 +- ...tionEndpointConventionBuilderExtensions.cs | 0 .../src/AuthorizationMiddleware.cs | 0 .../src/AuthorizationPolicyMarkerService.cs} | 2 +- .../Policy => Policy/src}/IPolicyEvaluator.cs | 0 ...oft.AspNetCore.Authorization.Policy.csproj | 9 +- .../src}/PolicyAuthorizationResult.cs | 0 .../Policy => Policy/src}/PolicyEvaluator.cs | 0 .../src/PolicyServiceCollectionExtensions.cs | 33 ++++- .../Policy/src/Properties/AssemblyInfo.cs | 11 -- .../src/Properties/Resources.Designer.cs | 44 +++++++ .../Authorization/Policy/src/Resources.resx | 123 ++++++++++++++++++ .../AuthorizationAppBuilderExtensionsTests.cs | 6 +- .../test/AuthorizationMiddlewareTests.cs | 5 - .../test/DefaultAuthorizationServiceTests.cs | 2 +- .../Microsoft.AspNetCore.SignalR.Core.csproj | 2 +- .../Microsoft.AspNetCore.SignalR.Core.csproj | 2 +- 38 files changed, 324 insertions(+), 139 deletions(-) create mode 100644 src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.csproj create mode 100644 src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.netstandard2.0.cs rename src/Http/{Http.Abstractions => Metadata}/src/IAllowAnonymous.cs (100%) rename src/Http/{Http.Abstractions => Metadata}/src/IAuthorizeData.cs (100%) create mode 100644 src/Http/Metadata/src/Microsoft.AspNetCore.Metadata.csproj rename src/Security/Authorization/Core/ref/{Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs => Microsoft.AspNetCore.Authorization.netstandard2.0.cs} (84%) rename src/Security/Authorization/{Core/src/Policy => Policy/src}/AuthorizationAppBuilderExtensions.cs (94%) rename src/Security/Authorization/{Core/src/Policy => Policy/src}/AuthorizationEndpointConventionBuilderExtensions.cs (100%) rename src/Security/Authorization/{Core => Policy}/src/AuthorizationMiddleware.cs (100%) rename src/Security/Authorization/{Core/src/AuthorizationMarkerService.cs => Policy/src/AuthorizationPolicyMarkerService.cs} (81%) rename src/Security/Authorization/{Core/src/Policy => Policy/src}/IPolicyEvaluator.cs (100%) rename src/Security/Authorization/{Core/src/Policy => Policy/src}/PolicyAuthorizationResult.cs (100%) rename src/Security/Authorization/{Core/src/Policy => Policy/src}/PolicyEvaluator.cs (100%) delete mode 100644 src/Security/Authorization/Policy/src/Properties/AssemblyInfo.cs create mode 100644 src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs create mode 100644 src/Security/Authorization/Policy/src/Resources.resx diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 067ba79788..092cfa3472 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -35,6 +35,7 @@ + diff --git a/eng/SharedFramework.Local.props b/eng/SharedFramework.Local.props index 089cea8bb5..2c605f63cd 100644 --- a/eng/SharedFramework.Local.props +++ b/eng/SharedFramework.Local.props @@ -33,6 +33,7 @@ + diff --git a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs index 6a70682b1a..899be43f0c 100644 --- a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs +++ b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs @@ -1,18 +1,6 @@ // 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.Authorization -{ - public partial interface IAllowAnonymous - { - } - public partial interface IAuthorizeData - { - string AuthenticationSchemes { get; set; } - string Policy { get; set; } - string Roles { get; set; } - } -} namespace Microsoft.AspNetCore.Builder { public abstract partial class EndpointBuilder diff --git a/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.csproj b/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.csproj new file mode 100644 index 0000000000..5bd3e643f1 --- /dev/null +++ b/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + + + + + + diff --git a/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.netstandard2.0.cs b/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.netstandard2.0.cs new file mode 100644 index 0000000000..effddb3203 --- /dev/null +++ b/src/Http/Metadata/ref/Microsoft.AspNetCore.Metadata.netstandard2.0.cs @@ -0,0 +1,15 @@ +// 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.Authorization +{ + public partial interface IAllowAnonymous + { + } + public partial interface IAuthorizeData + { + string AuthenticationSchemes { get; set; } + string Policy { get; set; } + string Roles { get; set; } + } +} diff --git a/src/Http/Http.Abstractions/src/IAllowAnonymous.cs b/src/Http/Metadata/src/IAllowAnonymous.cs similarity index 100% rename from src/Http/Http.Abstractions/src/IAllowAnonymous.cs rename to src/Http/Metadata/src/IAllowAnonymous.cs diff --git a/src/Http/Http.Abstractions/src/IAuthorizeData.cs b/src/Http/Metadata/src/IAuthorizeData.cs similarity index 100% rename from src/Http/Http.Abstractions/src/IAuthorizeData.cs rename to src/Http/Metadata/src/IAuthorizeData.cs diff --git a/src/Http/Metadata/src/Microsoft.AspNetCore.Metadata.csproj b/src/Http/Metadata/src/Microsoft.AspNetCore.Metadata.csproj new file mode 100644 index 0000000000..4c2b8fed22 --- /dev/null +++ b/src/Http/Metadata/src/Microsoft.AspNetCore.Metadata.csproj @@ -0,0 +1,12 @@ + + + + ASP.NET Core metadata. + netstandard2.0 + true + $(NoWarn);CS1591 + true + aspnetcore + + + diff --git a/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.csproj index 4771b24dcf..a68d86a042 100644 --- a/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.csproj @@ -5,6 +5,7 @@
+ diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index b78218944f..70f62b2cfe 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -26,6 +26,7 @@ Microsoft.AspNetCore.Routing.RouteCollection + diff --git a/src/Mvc/Mvc.Formatters.Json/src/Properties/AssemblyInfo.cs b/src/Mvc/Mvc.Formatters.Json/src/Properties/AssemblyInfo.cs index 95a17d6462..039f419cd4 100644 --- a/src/Mvc/Mvc.Formatters.Json/src/Properties/AssemblyInfo.cs +++ b/src/Mvc/Mvc.Formatters.Json/src/Properties/AssemblyInfo.cs @@ -5,4 +5,3 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc; [assembly: TypeForwardedTo(typeof(JsonResult))] - diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj index edcb7281ec..e965b33d18 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj @@ -1,13 +1,11 @@ - netcoreapp3.0 + netstandard2.0 - - - - - + + + diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs similarity index 84% rename from src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs rename to src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs index ef38eddb47..ab30a7c6d5 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netstandard2.0.cs @@ -42,12 +42,6 @@ namespace Microsoft.AspNetCore.Authorization public virtual System.Threading.Tasks.Task HandleAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context) { throw null; } protected abstract System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, TRequirement requirement, TResource resource); } - public partial class AuthorizationMiddleware - { - public AuthorizationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } - } public partial class AuthorizationOptions { public AuthorizationOptions() { } @@ -220,50 +214,11 @@ namespace Microsoft.AspNetCore.Authorization.Infrastructure protected override System.Threading.Tasks.Task HandleRequirementAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context, Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement requirement) { throw null; } } } -namespace Microsoft.AspNetCore.Authorization.Policy -{ - public partial interface IPolicyEvaluator - { - System.Threading.Tasks.Task AuthenticateAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Http.HttpContext context); - System.Threading.Tasks.Task AuthorizeAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Authentication.AuthenticateResult authenticationResult, Microsoft.AspNetCore.Http.HttpContext context, object resource); - } - public partial class PolicyAuthorizationResult - { - internal PolicyAuthorizationResult() { } - public bool Challenged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Forbidden { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Challenge() { throw null; } - public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Forbid() { throw null; } - public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Success() { throw null; } - } - public partial class PolicyEvaluator : Microsoft.AspNetCore.Authorization.Policy.IPolicyEvaluator - { - public PolicyEvaluator(Microsoft.AspNetCore.Authorization.IAuthorizationService authorization) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public virtual System.Threading.Tasks.Task AuthenticateAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Http.HttpContext context) { throw null; } - [System.Diagnostics.DebuggerStepThroughAttribute] - public virtual System.Threading.Tasks.Task AuthorizeAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Authentication.AuthenticateResult authenticationResult, Microsoft.AspNetCore.Http.HttpContext context, object resource) { throw null; } - } -} -namespace Microsoft.AspNetCore.Builder -{ - public static partial class AuthorizationAppBuilderExtensions - { - public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } - } - public static partial class AuthorizationEndpointConventionBuilderExtensions - { - public static TBuilder RequireAuthorization(this TBuilder builder) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } - public static TBuilder RequireAuthorization(this TBuilder builder, params Microsoft.AspNetCore.Authorization.IAuthorizeData[] authorizeData) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } - public static TBuilder RequireAuthorization(this TBuilder builder, params string[] policyNames) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } - } -} namespace Microsoft.Extensions.DependencyInjection { public static partial class AuthorizationServiceCollectionExtensions { - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorizationCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorizationCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } } } diff --git a/src/Security/Authorization/Core/src/AuthorizationOptions.cs b/src/Security/Authorization/Core/src/AuthorizationOptions.cs index d9121f60ba..c834441813 100644 --- a/src/Security/Authorization/Core/src/AuthorizationOptions.cs +++ b/src/Security/Authorization/Core/src/AuthorizationOptions.cs @@ -29,10 +29,10 @@ namespace Microsoft.AspNetCore.Authorization /// /// Gets or sets the fallback authorization policy used by - /// when no IAuthorizeData have been provided. As a result, the uses the fallback policy + /// when no IAuthorizeData have been provided. As a result, the AuthorizationMiddleware uses the fallback policy /// if there are no instances for a resource. If a resource has any /// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no - /// effect unless you have the middleware in your pipeline. It is not used in any way by the + /// effect unless you have the AuthorizationMiddleware in your pipeline. It is not used in any way by the /// default . /// public AuthorizationPolicy FallbackPolicy { get; set; } diff --git a/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs b/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs index c3b0dc580b..0f788cd5ad 100644 --- a/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs +++ b/src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; -using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection @@ -19,7 +18,7 @@ namespace Microsoft.Extensions.DependencyInjection ///
/// The to add services to. /// The so that additional calls can be chained. - public static IServiceCollection AddAuthorization(this IServiceCollection services) + public static IServiceCollection AddAuthorizationCore(this IServiceCollection services) { if (services == null) { @@ -32,11 +31,6 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient()); services.TryAddEnumerable(ServiceDescriptor.Transient()); - services.TryAddSingleton(); - - // Policy - services.TryAdd(ServiceDescriptor.Transient()); - return services; } @@ -46,20 +40,19 @@ namespace Microsoft.Extensions.DependencyInjection /// The to add services to. /// An action delegate to configure the provided . /// The so that additional calls can be chained. - public static IServiceCollection AddAuthorization(this IServiceCollection services, Action configure) + public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action configure) { if (services == null) { throw new ArgumentNullException(nameof(services)); } - if (configure == null) + if (configure != null) { - throw new ArgumentNullException(nameof(configure)); + services.Configure(configure); } - services.Configure(configure); - return services.AddAuthorization(); + return services.AddAuthorizationCore(); } } } diff --git a/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj b/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj index 726247c53b..ca6b8ce526 100644 --- a/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj @@ -1,11 +1,11 @@ - + ASP.NET Core authorization classes. Commonly used types: Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute Microsoft.AspNetCore.Authorization.AuthorizeAttribute - netcoreapp3.0 + netstandard2.0 true $(NoWarn);CS1591 true @@ -13,13 +13,7 @@ Microsoft.AspNetCore.Authorization.AuthorizeAttribute - - - - - - - + diff --git a/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs b/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs index aa5f527121..aaaa24d8c9 100644 --- a/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs +++ b/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs @@ -1,9 +1,10 @@ + // 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; using Microsoft.AspNetCore.Authorization; -// Microsoft.AspNetCore.Http.Abstractions +// Microsoft.AspNetCore.Metadata [assembly: TypeForwardedTo(typeof(IAuthorizeData))] [assembly: TypeForwardedTo(typeof(IAllowAnonymous))] diff --git a/src/Security/Authorization/Core/src/Properties/Resources.Designer.cs b/src/Security/Authorization/Core/src/Properties/Resources.Designer.cs index 876d0321b0..c83fa9ea5e 100644 --- a/src/Security/Authorization/Core/src/Properties/Resources.Designer.cs +++ b/src/Security/Authorization/Core/src/Properties/Resources.Designer.cs @@ -52,20 +52,6 @@ namespace Microsoft.AspNetCore.Authorization internal static string FormatException_RoleRequirementEmpty() => GetString("Exception_RoleRequirementEmpty"); - /// - /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. - /// - internal static string Exception_UnableToFindServices - { - get => GetString("Exception_UnableToFindServices"); - } - - /// - /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. - /// - internal static string FormatException_UnableToFindServices(object p0, object p1, object p2) - => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UnableToFindServices"), p0, p1, p2); - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Security/Authorization/Core/src/Resources.resx b/src/Security/Authorization/Core/src/Resources.resx index ebf1328616..a36e55d6b0 100644 --- a/src/Security/Authorization/Core/src/Resources.resx +++ b/src/Security/Authorization/Core/src/Resources.resx @@ -126,7 +126,4 @@ At least one role must be specified. - - Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. - \ No newline at end of file diff --git a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.csproj b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.csproj index 6f81120877..2abea2d038 100644 --- a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.csproj +++ b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.csproj @@ -6,5 +6,8 @@ + + + diff --git a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp3.0.cs b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp3.0.cs index 14f716c60c..571d21a03d 100644 --- a/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp3.0.cs +++ b/src/Security/Authorization/Policy/ref/Microsoft.AspNetCore.Authorization.Policy.netcoreapp3.0.cs @@ -1,11 +1,60 @@ // 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.Authorization +{ + public partial class AuthorizationMiddleware + { + public AuthorizationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider policyProvider) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } +} +namespace Microsoft.AspNetCore.Authorization.Policy +{ + public partial interface IPolicyEvaluator + { + System.Threading.Tasks.Task AuthenticateAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Http.HttpContext context); + System.Threading.Tasks.Task AuthorizeAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Authentication.AuthenticateResult authenticationResult, Microsoft.AspNetCore.Http.HttpContext context, object resource); + } + public partial class PolicyAuthorizationResult + { + internal PolicyAuthorizationResult() { } + public bool Challenged { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Forbidden { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Succeeded { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Challenge() { throw null; } + public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Forbid() { throw null; } + public static Microsoft.AspNetCore.Authorization.Policy.PolicyAuthorizationResult Success() { throw null; } + } + public partial class PolicyEvaluator : Microsoft.AspNetCore.Authorization.Policy.IPolicyEvaluator + { + public PolicyEvaluator(Microsoft.AspNetCore.Authorization.IAuthorizationService authorization) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AuthenticateAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public virtual System.Threading.Tasks.Task AuthorizeAsync(Microsoft.AspNetCore.Authorization.AuthorizationPolicy policy, Microsoft.AspNetCore.Authentication.AuthenticateResult authenticationResult, Microsoft.AspNetCore.Http.HttpContext context, object resource) { throw null; } + } +} +namespace Microsoft.AspNetCore.Builder +{ + public static partial class AuthorizationAppBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } + public static partial class AuthorizationEndpointConventionBuilderExtensions + { + public static TBuilder RequireAuthorization(this TBuilder builder) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } + public static TBuilder RequireAuthorization(this TBuilder builder, params Microsoft.AspNetCore.Authorization.IAuthorizeData[] authorizeData) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } + public static TBuilder RequireAuthorization(this TBuilder builder, params string[] policyNames) where TBuilder : Microsoft.AspNetCore.Builder.IEndpointConventionBuilder { throw null; } + } +} namespace Microsoft.Extensions.DependencyInjection { public static partial class PolicyServiceCollectionExtensions { - [System.ObsoleteAttribute("AddAuthorizationPolicyEvaluator is obsolete and will be removed in a future release. Use AddAuthorization instead.")] + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAuthorizationPolicyEvaluator(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } } } diff --git a/src/Security/Authorization/Core/src/Policy/AuthorizationAppBuilderExtensions.cs b/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs similarity index 94% rename from src/Security/Authorization/Core/src/Policy/AuthorizationAppBuilderExtensions.cs rename to src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs index a34551243b..41cfcb96fb 100644 --- a/src/Security/Authorization/Core/src/Policy/AuthorizationAppBuilderExtensions.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs @@ -34,11 +34,11 @@ namespace Microsoft.AspNetCore.Builder { // Verify that AddAuthorizationPolicy was called before calling UseAuthorization // We use the AuthorizationPolicyMarkerService to ensure all the services were added. - if (app.ApplicationServices.GetService(typeof(AuthorizationMarkerService)) == null) + if (app.ApplicationServices.GetService(typeof(AuthorizationPolicyMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatException_UnableToFindServices( nameof(IServiceCollection), - nameof(AuthorizationServiceCollectionExtensions.AddAuthorization), + nameof(PolicyServiceCollectionExtensions.AddAuthorization), "ConfigureServices(...)")); } } diff --git a/src/Security/Authorization/Core/src/Policy/AuthorizationEndpointConventionBuilderExtensions.cs b/src/Security/Authorization/Policy/src/AuthorizationEndpointConventionBuilderExtensions.cs similarity index 100% rename from src/Security/Authorization/Core/src/Policy/AuthorizationEndpointConventionBuilderExtensions.cs rename to src/Security/Authorization/Policy/src/AuthorizationEndpointConventionBuilderExtensions.cs diff --git a/src/Security/Authorization/Core/src/AuthorizationMiddleware.cs b/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs similarity index 100% rename from src/Security/Authorization/Core/src/AuthorizationMiddleware.cs rename to src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs diff --git a/src/Security/Authorization/Core/src/AuthorizationMarkerService.cs b/src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs similarity index 81% rename from src/Security/Authorization/Core/src/AuthorizationMarkerService.cs rename to src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs index 122d237e8c..9061891e43 100644 --- a/src/Security/Authorization/Core/src/AuthorizationMarkerService.cs +++ b/src/Security/Authorization/Policy/src/AuthorizationPolicyMarkerService.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Authorization.Policy { - internal class AuthorizationMarkerService + internal class AuthorizationPolicyMarkerService { } } diff --git a/src/Security/Authorization/Core/src/Policy/IPolicyEvaluator.cs b/src/Security/Authorization/Policy/src/IPolicyEvaluator.cs similarity index 100% rename from src/Security/Authorization/Core/src/Policy/IPolicyEvaluator.cs rename to src/Security/Authorization/Policy/src/IPolicyEvaluator.cs diff --git a/src/Security/Authorization/Policy/src/Microsoft.AspNetCore.Authorization.Policy.csproj b/src/Security/Authorization/Policy/src/Microsoft.AspNetCore.Authorization.Policy.csproj index 2954d1564a..ecd83793a4 100644 --- a/src/Security/Authorization/Policy/src/Microsoft.AspNetCore.Authorization.Policy.csproj +++ b/src/Security/Authorization/Policy/src/Microsoft.AspNetCore.Authorization.Policy.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core authorization policy helper classes. @@ -9,8 +9,15 @@ aspnetcore;authorization + + + + + + + diff --git a/src/Security/Authorization/Core/src/Policy/PolicyAuthorizationResult.cs b/src/Security/Authorization/Policy/src/PolicyAuthorizationResult.cs similarity index 100% rename from src/Security/Authorization/Core/src/Policy/PolicyAuthorizationResult.cs rename to src/Security/Authorization/Policy/src/PolicyAuthorizationResult.cs diff --git a/src/Security/Authorization/Core/src/Policy/PolicyEvaluator.cs b/src/Security/Authorization/Policy/src/PolicyEvaluator.cs similarity index 100% rename from src/Security/Authorization/Core/src/Policy/PolicyEvaluator.cs rename to src/Security/Authorization/Policy/src/PolicyEvaluator.cs diff --git a/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs b/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs index a4eb53ba21..d24a5a243f 100644 --- a/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs +++ b/src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -13,11 +14,10 @@ namespace Microsoft.Extensions.DependencyInjection public static class PolicyServiceCollectionExtensions { /// - /// Adds authorization policy services to the specified . + /// Adds the authorization policy evaluator service to the specified . /// /// The to add services to. /// The so that additional calls can be chained. - [Obsolete("AddAuthorizationPolicyEvaluator is obsolete and will be removed in a future release. Use AddAuthorization instead.")] public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services) { if (services == null) @@ -25,7 +25,34 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(services)); } - services.AddAuthorization(); + services.TryAddSingleton(); + services.TryAdd(ServiceDescriptor.Transient()); + return services; + } + + /// + /// Adds authorization policy services to the specified . + /// + /// The to add services to. + /// The so that additional calls can be chained. + public static IServiceCollection AddAuthorization(this IServiceCollection services) + => services.AddAuthorization(configure: null); + + /// + /// Adds authorization policy services to the specified . + /// + /// The to add services to. + /// An action delegate to configure the provided . + /// The so that additional calls can be chained. + public static IServiceCollection AddAuthorization(this IServiceCollection services, Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddAuthorizationCore(configure); + services.AddAuthorizationPolicyEvaluator(); return services; } } diff --git a/src/Security/Authorization/Policy/src/Properties/AssemblyInfo.cs b/src/Security/Authorization/Policy/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 890d26fd78..0000000000 --- a/src/Security/Authorization/Policy/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +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; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Authorization.Policy; - -// Microsoft.AspNetCore.Authorization -[assembly: TypeForwardedTo(typeof(IPolicyEvaluator))] -[assembly: TypeForwardedTo(typeof(PolicyAuthorizationResult))] -[assembly: TypeForwardedTo(typeof(PolicyEvaluator))] diff --git a/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs b/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..d4e50a8631 --- /dev/null +++ b/src/Security/Authorization/Policy/src/Properties/Resources.Designer.cs @@ -0,0 +1,44 @@ +// +namespace Microsoft.AspNetCore.Authorization.Policy +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Authorization.Policy.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string Exception_UnableToFindServices + { + get => GetString("Exception_UnableToFindServices"); + } + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string FormatException_UnableToFindServices(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UnableToFindServices"), p0, p1, p2); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Security/Authorization/Policy/src/Resources.resx b/src/Security/Authorization/Policy/src/Resources.resx new file mode 100644 index 0000000000..15d6f7d53c --- /dev/null +++ b/src/Security/Authorization/Policy/src/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + + \ No newline at end of file diff --git a/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs b/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs index 1431364b1f..aa888daf80 100644 --- a/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs +++ b/src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs @@ -2,8 +2,6 @@ // 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.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization.Test.TestObjects; @@ -73,9 +71,7 @@ namespace Microsoft.AspNetCore.Authorization.Test { var services = new ServiceCollection(); -#pragma warning disable CS0618 // Type or member is obsolete - services.AddAuthorizationPolicyEvaluator(); -#pragma warning restore CS0618 // Type or member is obsolete + services.AddAuthorization(); services.AddLogging(); services.AddSingleton(authenticationService); diff --git a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs index 289a9d0d07..86858f4fe1 100644 --- a/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs +++ b/src/Security/Authorization/test/AuthorizationMiddlewareTests.cs @@ -2,18 +2,13 @@ // 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.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.AspNetCore.Authorization.Test.TestObjects; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Moq; using Xunit; diff --git a/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs b/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs index 1d048dc164..d0fe9a62e9 100644 --- a/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs +++ b/src/Security/Authorization/test/DefaultAuthorizationServiceTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Authorization.Test private IAuthorizationService BuildAuthorizationService(Action setupServices = null) { var services = new ServiceCollection(); - services.AddAuthorization(); + services.AddAuthorizationCore(); services.AddLogging(); services.AddOptions(); setupServices?.Invoke(services); diff --git a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.csproj b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.csproj index c09bfa6d9a..b49f53e40d 100644 --- a/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.csproj +++ b/src/SignalR/server/Core/ref/Microsoft.AspNetCore.SignalR.Core.csproj @@ -5,7 +5,7 @@
- + 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 03ffc0d44b..8d8194fbc7 100644 --- a/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj +++ b/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj @@ -15,7 +15,7 @@ - + From 663c83c14035b471f80496d0ba377a3842c741c9 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 20 May 2019 17:51:06 -0700 Subject: [PATCH 23/41] Fix build failures with new VS (#10411) --- src/SignalR/common/Shared/AsyncEnumerableAdapters.cs | 3 ++- .../server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SignalR/common/Shared/AsyncEnumerableAdapters.cs b/src/SignalR/common/Shared/AsyncEnumerableAdapters.cs index 6ebd1dcb25..d997233e43 100644 --- a/src/SignalR/common/Shared/AsyncEnumerableAdapters.cs +++ b/src/SignalR/common/Shared/AsyncEnumerableAdapters.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -23,7 +24,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal return new CancelableTypedAsyncEnumerable(asyncEnumerable, cts); } - public static async IAsyncEnumerable MakeAsyncEnumerableFromChannel(ChannelReader channel, CancellationToken cancellationToken = default) + public static async IAsyncEnumerable MakeAsyncEnumerableFromChannel(ChannelReader channel, [EnumeratorCancellation] CancellationToken cancellationToken = default) { await foreach (var item in channel.ReadAllAsync(cancellationToken)) { diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs index 68d02497de..f99cb6c76e 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Channels; @@ -851,7 +852,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests return channel.Reader; } - public async IAsyncEnumerable CancelableStreamGeneratedAsyncEnumerable(CancellationToken token) + public async IAsyncEnumerable CancelableStreamGeneratedAsyncEnumerable([EnumeratorCancellation] CancellationToken token) { _tcsService.StartedMethod.SetResult(null); await token.WaitForCancellationAsync(); From 47d39501a5034703c511bf48086920be65cbb6b8 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 21 May 2019 08:34:28 -0700 Subject: [PATCH 24/41] Upload native symbols for ANCM shim. (#10297) --- build/SharedFx.targets | 1 + eng/ProjectReferences.props | 1 + .../Microsoft.AspNetCore.ANCMSymbols.csproj | 37 +++++++++++++++++++ .../IIS/AspNetCoreModuleV2/Symbols/_._ | 0 src/Servers/IIS/build/assets.props | 1 + 5 files changed, 40 insertions(+) create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/Symbols/Microsoft.AspNetCore.ANCMSymbols.csproj create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/Symbols/_._ diff --git a/build/SharedFx.targets b/build/SharedFx.targets index b1841de154..6aa51bf34e 100644 --- a/build/SharedFx.targets +++ b/build/SharedFx.targets @@ -12,6 +12,7 @@ + diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 0ac8db756b..6897240033 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -49,6 +49,7 @@ + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/Symbols/Microsoft.AspNetCore.ANCMSymbols.csproj b/src/Servers/IIS/AspNetCoreModuleV2/Symbols/Microsoft.AspNetCore.ANCMSymbols.csproj new file mode 100644 index 0000000000..8a17d1970b --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/Symbols/Microsoft.AspNetCore.ANCMSymbols.csproj @@ -0,0 +1,37 @@ + + + + + netcoreapp3.0 + true + $(PackNativeAssets) + false + runtimes/$(TargetRuntimeIdentifier)/native/ + + $(TargetsForTfmSpecificBuildOutput); + AddPackNativeComponents + + $(MSBuildProjectName).$(TargetRuntimeIdentifier) + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Servers/IIS/AspNetCoreModuleV2/Symbols/_._ b/src/Servers/IIS/AspNetCoreModuleV2/Symbols/_._ new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Servers/IIS/build/assets.props b/src/Servers/IIS/build/assets.props index f6a7360235..4015e2338c 100644 --- a/src/Servers/IIS/build/assets.props +++ b/src/Servers/IIS/build/assets.props @@ -2,6 +2,7 @@ true + false x64 $(Platform) Win32 From cebd9000e954315999ce5a4d891952bbb86cf622 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Tue, 21 May 2019 08:53:01 -0700 Subject: [PATCH 25/41] [master] Update dependencies from Arcade and dotnet/arcadeaspnet/AspNetCore-Tooling (#10359) * Update dependencies from https://github.com/dotnet/arcade build 20190520.1 - Microsoft.DotNet.GenAPI - 1.0.0-beta.19270.1 - Microsoft.DotNet.Helix.Sdk - 2.0.0-beta.19270.1 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190520.2 - Microsoft.NET.Sdk.Razor - 3.0.0-preview6.19270.2 - Microsoft.CodeAnalysis.Razor - 3.0.0-preview6.19270.2 - Microsoft.AspNetCore.Razor.Language - 3.0.0-preview6.19270.2 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 3.0.0-preview6.19270.2 --- eng/Version.Details.xml | 24 ++++++++++++------------ eng/Versions.props | 10 +++++----- global.json | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ff0b959575..fc32cbe01a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,21 +9,21 @@ --> - + https://github.com/aspnet/AspNetCore-Tooling - 0cbfa9bc5f1096e8bd1d3b0e9101752d14ed63f2 + 62e33dac1d5ec88ab15d7af694c1adf29ffc4c59 - + https://github.com/aspnet/AspNetCore-Tooling - 0cbfa9bc5f1096e8bd1d3b0e9101752d14ed63f2 + 62e33dac1d5ec88ab15d7af694c1adf29ffc4c59 - + https://github.com/aspnet/AspNetCore-Tooling - 0cbfa9bc5f1096e8bd1d3b0e9101752d14ed63f2 + 62e33dac1d5ec88ab15d7af694c1adf29ffc4c59 - + https://github.com/aspnet/AspNetCore-Tooling - 0cbfa9bc5f1096e8bd1d3b0e9101752d14ed63f2 + 62e33dac1d5ec88ab15d7af694c1adf29ffc4c59 https://github.com/aspnet/EntityFrameworkCore @@ -384,13 +384,13 @@ https://github.com/aspnet/Extensions 8dfb4ece7ca9a6dea14264dafc38a0c953874559 - + https://github.com/dotnet/arcade - 30682cda0dd7ca1765463749dd91ec3cfec75eb9 + e913fb3b02d4089a91ff91c041c5f6e7c29038b0 - + https://github.com/dotnet/arcade - 30682cda0dd7ca1765463749dd91ec3cfec75eb9 + e913fb3b02d4089a91ff91c041c5f6e7c29038b0 https://github.com/aspnet/Extensions diff --git a/eng/Versions.props b/eng/Versions.props index c815033082..bc32721fd6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -17,7 +17,7 @@ --> - 1.0.0-beta.19262.1 + 1.0.0-beta.19270.1 3.0.0-preview6-27714-15 3.0.0-preview6-27714-15 @@ -114,10 +114,10 @@ 3.0.0-preview6.19252.4 3.0.0-preview6.19252.4 - 3.0.0-preview6.19265.1 - 3.0.0-preview6.19265.1 - 3.0.0-preview6.19265.1 - 3.0.0-preview6.19265.1 + 3.0.0-preview6.19270.2 + 3.0.0-preview6.19270.2 + 3.0.0-preview6.19270.2 + 3.0.0-preview6.19270.2 - + false diff --git a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs index 53597ad893..1817a4c302 100644 --- a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs +++ b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6549 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10426")] public void FindsReferenceAssemblyGraph_ForStandaloneApp() { // Arrange diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index c15d1d9bac..be4994a83b 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -2577,7 +2577,7 @@ namespace Microsoft.AspNetCore.Components.Test } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7487 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7487")] public async Task CanTriggerEventHandlerDisposedInEarlierPendingBatchAsync() { // This represents the scenario where the same event handler is being triggered diff --git a/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs b/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs index 9006c778c7..65c542f499 100644 --- a/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs +++ b/src/DataProtection/Extensions/test/DataProtectionProviderTests.cs @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.DataProtection [ConditionalFact] [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2177", FlakyOn.AzP.Windows)] [X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public void System_UsesProvidedDirectoryAndCertificate() { var filePath = Path.Combine(GetTestFilesPath(), "TestCert.pfx"); @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.DataProtection [ConditionalFact] [X509StoreIsAvailable(StoreName.My, StoreLocation.CurrentUser)] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public void System_UsesProvidedCertificateNotFromStore() { using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj index 416f78e75c..6da74488a4 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj @@ -4,7 +4,7 @@ netcoreapp3.0 - + false diff --git a/src/Hosting/Hosting/test/WebHostTests.cs b/src/Hosting/Hosting/test/WebHostTests.cs index 5d3e7139c7..3d4a67b076 100644 --- a/src/Hosting/Hosting/test/WebHostTests.cs +++ b/src/Hosting/Hosting/test/WebHostTests.cs @@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Hosting } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7291 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7291")] public async Task WebHostStopAsyncUsesDefaultTimeoutIfGivenTokenDoesNotFire() { var data = new Dictionary @@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Hosting } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7291 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7291")] public void WebHostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown() { using (var host = CreateBuilder() diff --git a/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj b/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj index 1c92dddbaa..7a273f333d 100644 --- a/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj +++ b/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj @@ -4,7 +4,7 @@ netcoreapp3.0 - + false diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs index 0d7210cd0d..7e6124e146 100644 --- a/src/Hosting/test/FunctionalTests/ShutdownTests.cs +++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs @@ -49,8 +49,11 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests { var logger = loggerFactory.CreateLogger(testName); +// https://github.com/aspnet/AspNetCore/issues/8247 +#pragma warning disable 0618 var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets", "Microsoft.AspNetCore.Hosting.TestSites"); +#pragma warning restore 0618 var deploymentParameters = new DeploymentParameters( applicationPath, diff --git a/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs index 88988a779e..216cf1cff9 100644 --- a/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs +++ b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs @@ -28,7 +28,10 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests { var logger = loggerFactory.CreateLogger(nameof(InjectedStartup_DefaultApplicationNameIsEntryAssembly)); +// https://github.com/aspnet/AspNetCore/issues/8247 +#pragma warning disable 0618 var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets", "IStartupInjectionAssemblyName"); +#pragma warning restore 0618 var deploymentParameters = new DeploymentParameters(variant) { diff --git a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs index 40a22ff1a7..0fb9d68e00 100644 --- a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs +++ b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/ConfigureSigningCredentialsTests.cs @@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer UnsafeEphemeralKeySet : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? X509KeyStorageFlags.PersistKeySet : X509KeyStorageFlags.DefaultKeySet); - [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [ConditionalFact] + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] [FrameworkSkipCondition(RuntimeFrameworks.CLR)] public void Configure_AddsDevelopmentKeyFromConfiguration() { @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public void Configure_LoadsPfxCertificateCredentialFromConfiguration() { // Arrange @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public void Configure_LoadsCertificateStoreCertificateCredentialFromConfiguration() { try diff --git a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs index 8551f9ccae..20bd91c88f 100644 --- a/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs +++ b/src/Identity/ApiAuthorization.IdentityServer/test/Configuration/SigningKeysLoaderTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration [Fact] public void LoadFromFile_ThrowsIfFileDoesNotExist() { - // Arrange, Act & Assert + // Arrange, Act & Assert var exception = Assert.Throws(() => SigningKeysLoader.LoadFromFile("./nonexisting.pfx", "", DefaultFlags)); Assert.Equal($"There was an error loading the certificate. The file './nonexisting.pfx' was not found.", exception.Message); } @@ -58,8 +58,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration Assert.Equal("Couldn't find a valid certificate with subject 'Invalid' on the 'CurrentUser\\My'", exception.Message); } - [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [ConditionalFact] + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public static void LoadFromStoreCert_SkipsCertificatesNotYetValid() { try @@ -81,8 +81,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration } } - [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [ConditionalFact] + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public static void LoadFromStoreCert_PrefersCertificatesCloserToExpirationDate() { try @@ -104,8 +104,8 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer.Configuration } } - [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6720 + [ConditionalFact] + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6720")] public static void LoadFromStoreCert_SkipsExpiredCertificates() { try diff --git a/src/Middleware/CORS/test/FunctionalTests/CORS.FunctionalTests.csproj b/src/Middleware/CORS/test/FunctionalTests/CORS.FunctionalTests.csproj index 8bb04bbf54..1c0e5a12a1 100644 --- a/src/Middleware/CORS/test/FunctionalTests/CORS.FunctionalTests.csproj +++ b/src/Middleware/CORS/test/FunctionalTests/CORS.FunctionalTests.csproj @@ -6,7 +6,7 @@ $(DefaultItemExcludes);node_modules\**\* - + false diff --git a/src/Middleware/CORS/test/FunctionalTests/CorsMiddlewareFunctionalTest.cs b/src/Middleware/CORS/test/FunctionalTests/CorsMiddlewareFunctionalTest.cs index 717f67a07f..2990bd53a9 100644 --- a/src/Middleware/CORS/test/FunctionalTests/CorsMiddlewareFunctionalTest.cs +++ b/src/Middleware/CORS/test/FunctionalTests/CorsMiddlewareFunctionalTest.cs @@ -68,7 +68,10 @@ namespace FunctionalTests private static async Task CreateDeployments(ILoggerFactory loggerFactory, string startup) { + // https://github.com/aspnet/AspNetCore/issues/7990 +#pragma warning disable 0618 var solutionPath = TestPathUtilities.GetSolutionRootDirectory("Middleware"); +#pragma warning restore 0618 var configuration = #if RELEASE diff --git a/src/MusicStore/test/MusicStore.E2ETests/Common/Helpers.cs b/src/MusicStore/test/MusicStore.E2ETests/Common/Helpers.cs index 954cb9dea5..eb761e21a5 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/Common/Helpers.cs +++ b/src/MusicStore/test/MusicStore.E2ETests/Common/Helpers.cs @@ -9,7 +9,10 @@ namespace E2ETests { public static string GetApplicationPath() { + // https://github.com/aspnet/AspNetCore/issues/8343 +#pragma warning disable 0618 var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("MusicStore"); +#pragma warning restore 0618 return Path.GetFullPath(Path.Combine(solutionDirectory, "samples", "MusicStore")); } diff --git a/src/MusicStore/test/MusicStore.E2ETests/MusicStore.E2ETests.csproj b/src/MusicStore/test/MusicStore.E2ETests/MusicStore.E2ETests.csproj index 5a2d81e394..affbb20ef3 100644 --- a/src/MusicStore/test/MusicStore.E2ETests/MusicStore.E2ETests.csproj +++ b/src/MusicStore/test/MusicStore.E2ETests/MusicStore.E2ETests.csproj @@ -9,7 +9,7 @@ $(WarningsNotAsErrors);xUnit1004 $(NoWarn);NU1605 - + false false false diff --git a/src/Mvc/Mvc.Analyzers/test/Infrastructure/MvcTestSource.cs b/src/Mvc/Mvc.Analyzers/test/Infrastructure/MvcTestSource.cs index 89f141ef95..8b304a0f94 100644 --- a/src/Mvc/Mvc.Analyzers/test/Infrastructure/MvcTestSource.cs +++ b/src/Mvc/Mvc.Analyzers/test/Infrastructure/MvcTestSource.cs @@ -27,13 +27,16 @@ namespace Microsoft.AspNetCore.Mvc private static string GetProjectDirectory() { - // On helix we use the published test files + // On helix we use the published test files if (SkipOnHelixAttribute.OnHelix()) { return AppContext.BaseDirectory; } +// https://github.com/aspnet/AspNetCore/issues/9431 +#pragma warning disable 0618 var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Mvc"); +#pragma warning restore 0618 var projectDirectory = Path.Combine(solutionDirectory, "Mvc.Analyzers", "test"); return projectDirectory; } diff --git a/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj b/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj index 522919a556..ab2cabb576 100644 --- a/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj +++ b/src/Mvc/Mvc.Analyzers/test/Mvc.Analyzers.Test.csproj @@ -6,7 +6,7 @@ Microsoft.AspNetCore.Mvc.Analyzers - + false diff --git a/src/Mvc/Mvc.Api.Analyzers/test/Infrastructure/MvcTestSource.cs b/src/Mvc/Mvc.Api.Analyzers/test/Infrastructure/MvcTestSource.cs index fb20bd8cbf..6548f4306b 100644 --- a/src/Mvc/Mvc.Api.Analyzers/test/Infrastructure/MvcTestSource.cs +++ b/src/Mvc/Mvc.Api.Analyzers/test/Infrastructure/MvcTestSource.cs @@ -27,13 +27,16 @@ namespace Microsoft.AspNetCore.Mvc private static string GetProjectDirectory() { - // On helix we use the published test files + // On helix we use the published test files if (SkipOnHelixAttribute.OnHelix()) { return AppContext.BaseDirectory; } +// https://github.com/aspnet/AspNetCore/issues/9431 +#pragma warning disable 0618 var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Mvc"); +#pragma warning restore 0618 var projectDirectory = Path.Combine(solutionDirectory, "Mvc.Api.Analyzers", "test"); return projectDirectory; } diff --git a/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj b/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj index 6a29876594..c81ef1db1d 100644 --- a/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj +++ b/src/Mvc/Mvc.Api.Analyzers/test/Mvc.Api.Analyzers.Test.csproj @@ -5,7 +5,7 @@ Microsoft.AspNetCore.Mvc.Api.Analyzers - + false diff --git a/src/Mvc/test/Mvc.FunctionalTests/Infrastructure/ResourceFile.cs b/src/Mvc/test/Mvc.FunctionalTests/Infrastructure/ResourceFile.cs index 97c51a0d6e..013847e683 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/Infrastructure/ResourceFile.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/Infrastructure/ResourceFile.cs @@ -191,7 +191,11 @@ namespace Microsoft.AspNetCore.Mvc { // The build system compiles every file under the resources folder as a resource available at runtime // with the same name as the file name. Need to update this file on disc. + +// https://github.com/aspnet/AspNetCore/issues/10423 +#pragma warning disable 0618 var solutionPath = TestPathUtilities.GetSolutionRootDirectory("Mvc"); +#pragma warning restore 0618 var projectPath = Path.Combine(solutionPath, "test", assembly.GetName().Name); var fullPath = Path.Combine(projectPath, resourceName); WriteFile(fullPath, content); diff --git a/src/Mvc/test/Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/src/Mvc/test/Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 2a8c7e0504..51388a1c39 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/src/Mvc/test/Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -9,7 +9,7 @@ Mvc.FunctionalTests false - + false diff --git a/src/Security/Authentication/test/SecureDataFormatTests.cs b/src/Security/Authentication/test/SecureDataFormatTests.cs index 4a821ca2db..9ff1c26728 100644 --- a/src/Security/Authentication/test/SecureDataFormatTests.cs +++ b/src/Security/Authentication/test/SecureDataFormatTests.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Authentication.DataHandler } [ConditionalFact] - [SkipOnHelix] + [SkipOnHelix("https://github.com/aspnet/AspNetCore-Internal/issues/1974")] public void UnprotectWithDifferentPurposeFails() { var provider = ServiceProvider.GetRequiredService(); diff --git a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs index b1da8f1093..3cb8d3a1c8 100644 --- a/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs +++ b/src/Servers/IIS/IIS/benchmarks/IIS.Performance/StartupTimeBenchmark.cs @@ -20,11 +20,15 @@ namespace Microsoft.AspNetCore.Server.IIS.Performance [IterationSetup] public void Setup() { +// Deployers do not work in distributed environments +// see https://github.com/aspnet/AspNetCore/issues/10268 and https://github.com/aspnet/Extensions/issues/1697 +#pragma warning disable 0618 var deploymentParameters = new DeploymentParameters(Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "test/testassets/InProcessWebSite"), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { +#pragma warning restore 0618 ServerConfigTemplateContent = File.ReadAllText("IISExpress.config"), SiteName = "HttpTestSite", TargetFramework = "netcoreapp2.1", diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index d55fc4bf4b..009ecd8319 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -113,8 +113,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [SkipIfNotAdmin] [RequiresNewShim] [RequiresIIS(IISCapability.PoolEnvironmentVariables)] - [SkipOnHelix] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2221", FlakyOn.Helix.All)] + [SkipOnHelix("https://github.com/aspnet/AspNetCore-Internal/issues/2221")] public async Task StartsWithDotnetInstallLocation(RuntimeArchitecture runtimeArchitecture) { var deploymentParameters = Fixture.GetBaseDeploymentParameters(); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs index 3e09818933..d9aac2dac5 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/PublishedApplicationPublisher.cs @@ -42,7 +42,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private string GetProjectReferencePublishLocation(DeploymentParameters deploymentParameters) { +// Deployers do not work in distributed environments +// see https://github.com/aspnet/AspNetCore/issues/10268 and https://github.com/aspnet/Extensions/issues/1697 +#pragma warning disable 0618 var testAssetsBasePath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "IIS", "test", "testassets", _applicationName); +#pragma warning restore 0618 var configuration = this.GetType().GetTypeInfo().Assembly.GetCustomAttribute().Configuration; var path = Path.Combine(testAssetsBasePath, "bin", configuration, deploymentParameters.TargetFramework, "publish", GetProfileName(deploymentParameters)); return path; diff --git a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/MofFileTests.cs b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/MofFileTests.cs index 8cc6e8c9ac..df0e867854 100644 --- a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/MofFileTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/MofFileTests.cs @@ -15,11 +15,14 @@ namespace IIS.FunctionalTests [ConditionalFact] [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] [RequiresIIS(IISCapability.TracingModule)] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2222", FlakyOn.Helix.All)] - [SkipOnHelix] + [SkipOnHelix("https://github.com/aspnet/AspNetCore-Internal/issues/2222")] public void CheckMofFile() { +// This test code needs to be updated to support distributed testing. +// See https://github.com/aspnet/AspNetCore-Internal/issues/2222 +#pragma warning disable 0618 var path = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("IISIntegration"), "aspnetcoremodulev2", "aspnetcore", "ancm.mof"); +#pragma warning restore 0618 var process = Process.Start("mofcomp.exe", path); process.WaitForExit(); Assert.Equal(0, process.ExitCode); diff --git a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs index 654b7b6fea..79c436a4ca 100644 --- a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs +++ b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalFact] [HostNameIsReachable] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7267")] public async Task RegisterAddresses_HostName_Success() { var hostName = Dns.GetHostName(); @@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [ConditionalFact] [HostNameIsReachable] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/7267")] public async Task ListenAnyIP_HostName_Success() { var hostName = Dns.GetHostName(); diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs index 3c392b00ce..59cfbbc2c9 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/HandshakeTests.cs @@ -20,7 +20,7 @@ using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 { [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] - [SkipOnHelix(Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] public class HandshakeTests : LoggedTest { diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs index 9175c4cf79..3e587da067 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 { [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] - [SkipOnHelix(Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 public class ShutdownTests : TestApplicationErrorLoggerLoggedTest { private static X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate(); @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 } [ConditionalFact] - [SkipOnHelix(Queues = "Fedora.28.Amd64.Open")] // https://github.com/aspnet/AspNetCore/issues/9985 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/9985", Queues = "Fedora.28.Amd64.Open")] // https://github.com/aspnet/AspNetCore/issues/9985 [Flaky("https://github.com/aspnet/AspNetCore/issues/9985", FlakyOn.All)] public async Task GracefulShutdownWaitsForRequestsToFinish() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/TlsTests.cs index a0214a49d0..283d0c2e2d 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 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2 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 => diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs index 3e1636490a..98dd378202 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/H2SpecTests.cs @@ -21,7 +21,7 @@ namespace Interop.FunctionalTests [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/corefx/issues/30492")] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81, SkipReason = "Missing Windows ALPN support: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation#Support")] - [SkipOnHelix(Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10428", Queues = "Debian.8.Amd64.Open")] // Debian 8 uses OpenSSL 1.0.1 which does not support HTTP/2 public class H2SpecTests : LoggedTest { [ConditionalTheory] diff --git a/src/Shared/test/SkipOnHelixAttribute.cs b/src/Shared/test/SkipOnHelixAttribute.cs index 2ad4018acc..e1a74467bc 100644 --- a/src/Shared/test/SkipOnHelixAttribute.cs +++ b/src/Shared/test/SkipOnHelixAttribute.cs @@ -12,6 +12,17 @@ namespace Microsoft.AspNetCore.Testing.xunit [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] public class SkipOnHelixAttribute : Attribute, ITestCondition { + public SkipOnHelixAttribute(string issueUrl) + { + if (string.IsNullOrEmpty(issueUrl)) + { + throw new ArgumentException(); + } + IssueUrl = issueUrl; + } + + public string IssueUrl { get; } + public bool IsMet { get @@ -33,7 +44,7 @@ namespace Microsoft.AspNetCore.Testing.xunit } public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue()); - + public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix"); } } diff --git a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs index e1849607d3..b5cf8cfaa2 100644 --- a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs +++ b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/6721 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")] public void EnsureCreateHttpsCertificate2_CreatesACertificate_WhenThereAreNoHttpsCertificates() { try diff --git a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs index 34cc22373f..5eb12ccb19 100644 --- a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs +++ b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/8267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] public async Task RunsWithDotnetWatchEnvVariable() { Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); diff --git a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs index c964bfe8c0..f85ef3bfcf 100644 --- a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs +++ b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs @@ -87,7 +87,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/8267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] public async Task ChangeExcludedFile() { await _app.StartWatcherAsync(); @@ -101,7 +101,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/8267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] public async Task ListsFiles() { await _app.PrepareAsync(); diff --git a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs index 29c2ed8739..eb5bd6ff7e 100644 --- a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs +++ b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs @@ -44,7 +44,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } [ConditionalFact] - [SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/8267 + [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")] public async Task RestartProcessThatTerminatesAfterFileChange() { await _app.StartWatcherAsync(); From 9c84558c9b740a278ff2d0231405a33b49cf1a6b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 21 May 2019 19:16:00 -0700 Subject: [PATCH 31/41] Add metadata to nuspec of packages that depend on the shared framework (#10429) --- build/repo.targets | 3 ++- .../RemoveSharedFrameworkDependencies.cs | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/build/repo.targets b/build/repo.targets index e9e3a0a780..fe9c1c7a3d 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -137,7 +137,8 @@ + FrameworkOnlyPackages="@(AspNetCoreAppReference)" + SharedFrameworkTargetFramework="netcoreapp$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)" /> diff --git a/build/tasks/RemoveSharedFrameworkDependencies.cs b/build/tasks/RemoveSharedFrameworkDependencies.cs index 415a3c8c58..3066bb5a89 100644 --- a/build/tasks/RemoveSharedFrameworkDependencies.cs +++ b/build/tasks/RemoveSharedFrameworkDependencies.cs @@ -6,8 +6,11 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Xml; +using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using NuGet.Frameworks; using NuGet.Packaging; using NuGet.Packaging.Core; @@ -22,6 +25,9 @@ namespace RepoTasks [Required] public ITaskItem[] FrameworkOnlyPackages { get; set; } + [Required] + public string SharedFrameworkTargetFramework { get; set; } + public override bool Execute() { Log.LogMessage("NuGet version = " + typeof(PackageArchiveReader).Assembly.GetName().Version); @@ -43,7 +49,7 @@ namespace RepoTasks using (var package = new ZipArchive(fileStream, ZipArchiveMode.Update)) using (var packageReader = new PackageArchiveReader(fileStream, leaveStreamOpen: true)) { - var dirty = false; + var referencesFrameworkOnlyAssembly = false; var nuspecFile = packageReader.GetNuspecFile(); using (var stream = package.OpenFile(nuspecFile)) { @@ -60,7 +66,7 @@ namespace RepoTasks { if (dependencyToRemove.Contains(dependency.Id)) { - dirty = true; + referencesFrameworkOnlyAssembly = true; Log.LogMessage($" Remove dependency on '{dependency.Id}'"); continue; } @@ -71,15 +77,27 @@ namespace RepoTasks updatedGroups.Add(updatedGroup); } - if (dirty) + if (referencesFrameworkOnlyAssembly) { packageBuilder.DependencyGroups.Clear(); packageBuilder.DependencyGroups.AddRange(updatedGroups); var updatedManifest = Manifest.Create(packageBuilder); + var inMemory = new MemoryStream(); + updatedManifest.Save(inMemory); + inMemory.Position = 0; + // Hack the raw nuspec to add the dependency + var rawNuspec = XDocument.Load(inMemory, LoadOptions.PreserveWhitespace); + var ns = rawNuspec.Root.GetDefaultNamespace(); + var metadata = rawNuspec.Root.Descendants(ns + "metadata").Single(); + metadata.Add( + new XElement(ns + "frameworkReferences", + new XElement(ns + "group", + new XAttribute("targetFramework", NuGetFramework.Parse(SharedFrameworkTargetFramework).GetFrameworkString()), + new XElement(ns + "frameworkReference", new XAttribute("name", "Microsoft.AspNetCore.App"))))); stream.Position = 0; stream.SetLength(0); - updatedManifest.Save(stream); + rawNuspec.Save(stream); } else { From f5879cc0d5bceb951d9b00bdee4ec6c106e34228 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Wed, 22 May 2019 06:14:50 -0700 Subject: [PATCH 32/41] Add new Negotiate Auth handler (#9831) --- eng/ProjectReferences.props | 1 + ...AspNetCore.Authentication.Negotiate.csproj | 11 + ....Authentication.Negotiate.netcoreapp3.0.cs | 64 +++ .../NegotiateAuthSample.csproj | 15 + .../samples/NegotiateAuthSample/Program.cs | 23 + .../Properties/launchSettings.json | 27 + .../samples/NegotiateAuthSample/Startup.cs | 40 ++ .../appsettings.Development.json | 10 + .../NegotiateAuthSample/appsettings.json | 9 + .../src/Events/AuthenticatedContext.cs | 25 + .../src/Events/AuthenticationFailedContext.cs | 31 ++ .../Negotiate/src/Events/ChallengeContext.cs | 38 ++ .../Negotiate/src/Events/NegotiateEvents.cs | 44 ++ .../Negotiate/src/Internal/INegotiateState.cs | 20 + .../src/Internal/INegotiateStateFactory.cs | 11 + .../Internal/NegotiateLoggingExtensions.cs | 71 +++ .../src/Internal/ReflectedNegotiateState.cs | 96 ++++ .../ReflectedNegotiateStateFactory.cs | 17 + ...AspNetCore.Authentication.Negotiate.csproj | 16 + .../Negotiate/src/NegotiateDefaults.cs | 16 + .../Negotiate/src/NegotiateExtensions.cs | 62 +++ .../Negotiate/src/NegotiateHandler.cs | 340 ++++++++++++ .../Negotiate/src/NegotiateOptions.cs | 39 ++ .../Negotiate/src/Properties/AssemblyInfo.cs | 6 + .../CrossMachineReadMe.md | 48 ++ .../CrossMachineTests.cs | 150 ++++++ ...entication.Negotiate.FunctionalTest.csproj | 26 + .../NegotiateHandlerFunctionalTests.cs | 307 +++++++++++ .../Negotiate.FunctionalTest/testCert.pfx | Bin 0 -> 1403 bytes .../test/Negotiate.Test/EventTests.cs | 393 ++++++++++++++ ...tCore.Authentication.Negotiate.Test.csproj | 19 + .../Negotiate.Test/NegotiateHandlerTests.cs | 506 ++++++++++++++++++ .../Controllers/AuthTestController.cs | 361 +++++++++++++ .../Negotiate.Client/Negotiate.Client.csproj | 17 + .../testassets/Negotiate.Client/Program.cs | 23 + .../Properties/launchSettings.json | 28 + .../testassets/Negotiate.Client/ReadMe.md | 4 + .../testassets/Negotiate.Client/Startup.cs | 39 ++ .../appsettings.Development.json | 9 + .../Negotiate.Client/appsettings.json | 10 + .../Controllers/AuthController.cs | 46 ++ .../Negotiate.Server/Negotiate.Server.csproj | 17 + .../testassets/Negotiate.Server/Program.cs | 43 ++ .../Properties/launchSettings.json | 30 ++ .../testassets/Negotiate.Server/ReadMe.md | 5 + .../testassets/Negotiate.Server/Startup.cs | 49 ++ .../appsettings.Development.json | 9 + .../Negotiate.Server/appsettings.json | 11 + .../Authentication/test/selfSigned.cer | Bin 762 -> 0 bytes src/Security/Security.sln | 59 ++ src/Servers/build.cmd | 3 + .../FunctionalTests/NtlmAuthenticationTest.cs | 15 +- .../ServerComparison.TestSites.csproj | 3 +- .../StartupNtlmAuthentication.cs | 32 +- 54 files changed, 3287 insertions(+), 7 deletions(-) create mode 100644 src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.csproj create mode 100644 src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp3.0.cs create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Program.cs create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Properties/launchSettings.json create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.Development.json create mode 100644 src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.json create mode 100644 src/Security/Authentication/Negotiate/src/Events/AuthenticatedContext.cs create mode 100644 src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs create mode 100644 src/Security/Authentication/Negotiate/src/Events/ChallengeContext.cs create mode 100644 src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs create mode 100644 src/Security/Authentication/Negotiate/src/Internal/INegotiateState.cs create mode 100644 src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs create mode 100644 src/Security/Authentication/Negotiate/src/Internal/NegotiateLoggingExtensions.cs create mode 100644 src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs create mode 100644 src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs create mode 100644 src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj create mode 100644 src/Security/Authentication/Negotiate/src/NegotiateDefaults.cs create mode 100644 src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs create mode 100644 src/Security/Authentication/Negotiate/src/NegotiateHandler.cs create mode 100644 src/Security/Authentication/Negotiate/src/NegotiateOptions.cs create mode 100644 src/Security/Authentication/Negotiate/src/Properties/AssemblyInfo.cs create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineTests.cs create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/Microsoft.AspNetCore.Authentication.Negotiate.FunctionalTest.csproj create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/NegotiateHandlerFunctionalTests.cs create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/testCert.pfx create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj create mode 100644 src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Controllers/AuthTestController.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Negotiate.Client.csproj create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Program.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Properties/launchSettings.json create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/ReadMe.md create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Startup.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.Development.json create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.json create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Controllers/AuthController.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Negotiate.Server.csproj create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Program.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Properties/launchSettings.json create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/ReadMe.md create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Startup.cs create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.Development.json create mode 100644 src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.json delete mode 100644 src/Security/Authentication/test/selfSigned.cer create mode 100644 src/Servers/build.cmd diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 6897240033..fa3ee5688b 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -63,6 +63,7 @@ + diff --git a/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.csproj b/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.csproj new file mode 100644 index 0000000000..ea9c20cd85 --- /dev/null +++ b/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.0 + + + + + + + diff --git a/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp3.0.cs b/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp3.0.cs new file mode 100644 index 0000000000..4f9f3a900e --- /dev/null +++ b/src/Security/Authentication/Negotiate/ref/Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp3.0.cs @@ -0,0 +1,64 @@ +// 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.Authentication.Negotiate +{ + public partial class AuthenticatedContext : Microsoft.AspNetCore.Authentication.ResultContext + { + public AuthenticatedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions)) { } + } + public partial class AuthenticationFailedContext : Microsoft.AspNetCore.Authentication.RemoteAuthenticationContext + { + public AuthenticationFailedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class ChallengeContext : Microsoft.AspNetCore.Authentication.PropertiesContext + { + public ChallengeContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } + public bool Handled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public void HandleResponse() { } + } + public static partial class NegotiateDefaults + { + public const string AuthenticationScheme = "Negotiate"; + } + public partial class NegotiateEvents + { + public NegotiateEvents() { } + public System.Func OnAuthenticated { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func OnAuthenticationFailed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func OnChallenge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public virtual System.Threading.Tasks.Task Authenticated(Microsoft.AspNetCore.Authentication.Negotiate.AuthenticatedContext context) { throw null; } + public virtual System.Threading.Tasks.Task AuthenticationFailed(Microsoft.AspNetCore.Authentication.Negotiate.AuthenticationFailedContext context) { throw null; } + public virtual System.Threading.Tasks.Task Challenge(Microsoft.AspNetCore.Authentication.Negotiate.ChallengeContext context) { throw null; } + } + public partial class NegotiateHandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler, Microsoft.AspNetCore.Authentication.IAuthenticationHandler, Microsoft.AspNetCore.Authentication.IAuthenticationRequestHandler + { + public NegotiateHandler(Microsoft.Extensions.Options.IOptionsMonitor options, Microsoft.Extensions.Logging.ILoggerFactory logger, System.Text.Encodings.Web.UrlEncoder encoder, Microsoft.AspNetCore.Authentication.ISystemClock clock) : base (default(Microsoft.Extensions.Options.IOptionsMonitor), default(Microsoft.Extensions.Logging.ILoggerFactory), default(System.Text.Encodings.Web.UrlEncoder), default(Microsoft.AspNetCore.Authentication.ISystemClock)) { } + protected new Microsoft.AspNetCore.Authentication.Negotiate.NegotiateEvents Events { get { throw null; } set { } } + protected override System.Threading.Tasks.Task CreateEventsAsync() { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected override System.Threading.Tasks.Task HandleAuthenticateAsync() { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + protected override System.Threading.Tasks.Task HandleChallengeAsync(Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.Task HandleRequestAsync() { throw null; } + } + public partial class NegotiateOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions + { + public NegotiateOptions() { } + public new Microsoft.AspNetCore.Authentication.Negotiate.NegotiateEvents Events { get { throw null; } set { } } + public bool PersistKerberosCredentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool PersistNtlmCredentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class NegotiateExtensions + { + public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddNegotiate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder) { throw null; } + public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddNegotiate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, System.Action configureOptions) { throw null; } + public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddNegotiate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) { throw null; } + public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddNegotiate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, string displayName, System.Action configureOptions) { throw null; } + } +} diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj new file mode 100644 index 0000000000..df3d2e036b --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + OutOfProcess + + + + + + + + + + diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Program.cs b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Program.cs new file mode 100644 index 0000000000..05f8132cb2 --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Program.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace NegotiateAuthSample +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Properties/launchSettings.json b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Properties/launchSettings.json new file mode 100644 index 0000000000..128e6417fa --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:6449", + "sslPort": 44369 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "NegotiateAuthSample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs new file mode 100644 index 0000000000..1643abe614 --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/Startup.cs @@ -0,0 +1,40 @@ +// 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; +using Microsoft.AspNetCore.Authentication.Negotiate; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace NegotiateAuthSample +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddAuthorization(options => + { + options.FallbackPolicy = options.DefaultPolicy; + }); + services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseDeveloperExceptionPage(); + app.UseAuthentication(); + app.UseAuthorization(); + app.Run(HandleRequest); + } + + public async Task HandleRequest(HttpContext context) + { + var user = context.User.Identity; + await context.Response.WriteAsync($"Authenticated? {user.IsAuthenticated}, Name: {user.Name}, Protocol: {context.Request.Protocol}"); + } + } +} diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.Development.json b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.Development.json new file mode 100644 index 0000000000..6d1c8438d2 --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information", + "Microsoft.AspNetCore.Authentication": "Debug" + } + } +} diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.json b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.json new file mode 100644 index 0000000000..7cb5ac8193 --- /dev/null +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Security/Authentication/Negotiate/src/Events/AuthenticatedContext.cs b/src/Security/Authentication/Negotiate/src/Events/AuthenticatedContext.cs new file mode 100644 index 0000000000..fee54f3e8d --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Events/AuthenticatedContext.cs @@ -0,0 +1,25 @@ +// 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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + /// + /// State for the Authenticated event. + /// + public class AuthenticatedContext : ResultContext + { + /// + /// Creates a new . + /// + /// + /// + /// + public AuthenticatedContext( + HttpContext context, + AuthenticationScheme scheme, + NegotiateOptions options) + : base(context, scheme, options) { } + } +} diff --git a/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs b/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs new file mode 100644 index 0000000000..d64c8da43e --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Events/AuthenticationFailedContext.cs @@ -0,0 +1,31 @@ +// 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.Http; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + /// + /// State for the AuthenticationFailed event. + /// + public class AuthenticationFailedContext : RemoteAuthenticationContext + { + /// + /// Creates a . + /// + /// + /// + /// + public AuthenticationFailedContext( + HttpContext context, + AuthenticationScheme scheme, + NegotiateOptions options) + : base(context, scheme, options, properties: null) { } + + /// + /// The exception that occured while processing the authentication. + /// + public Exception Exception { get; set; } + } +} diff --git a/src/Security/Authentication/Negotiate/src/Events/ChallengeContext.cs b/src/Security/Authentication/Negotiate/src/Events/ChallengeContext.cs new file mode 100644 index 0000000000..23b6ab041d --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Events/ChallengeContext.cs @@ -0,0 +1,38 @@ +// 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.Http; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + /// + /// State for the Challenge event. + /// + public class ChallengeContext : PropertiesContext + { + /// + /// Creates a new . + /// + /// + /// + /// + /// + public ChallengeContext( + HttpContext context, + AuthenticationScheme scheme, + NegotiateOptions options, + AuthenticationProperties properties) + : base(context, scheme, options, properties) { } + + /// + /// If true, will skip any default logic for this challenge. + /// + public bool Handled { get; private set; } + + /// + /// Skips any default logic for this challenge. + /// + public void HandleResponse() => Handled = true; + } +} diff --git a/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs b/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs new file mode 100644 index 0000000000..0d57be28eb --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Events/NegotiateEvents.cs @@ -0,0 +1,44 @@ +// 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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + /// + /// Specifies events which the invokes to enable developer control over the authentication process. + /// + public class NegotiateEvents + { + /// + /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. + /// + public Func OnAuthenticationFailed { get; set; } = context => Task.CompletedTask; + + /// + /// Invoked after the authentication is complete and a ClaimsIdentity has been generated. + /// + public Func OnAuthenticated { get; set; } = context => Task.CompletedTask; + + /// + /// Invoked before a challenge is sent back to the caller. + /// + public Func OnChallenge { get; set; } = context => Task.CompletedTask; + + /// + /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. + /// + public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); + + /// + /// Invoked after the authentication is complete and a ClaimsIdentity has been generated. + /// + public virtual Task Authenticated(AuthenticatedContext context) => OnAuthenticated(context); + + /// + /// Invoked before a challenge is sent back to the caller. + /// + public virtual Task Challenge(ChallengeContext context) => OnChallenge(context); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/INegotiateState.cs b/src/Security/Authentication/Negotiate/src/Internal/INegotiateState.cs new file mode 100644 index 0000000000..dbc215ef7d --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/INegotiateState.cs @@ -0,0 +1,20 @@ +// 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.Principal; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + // For testing + internal interface INegotiateState : IDisposable + { + string GetOutgoingBlob(string incomingBlob); + + bool IsCompleted { get; } + + string Protocol { get; } + + IIdentity GetIdentity(); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs b/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs new file mode 100644 index 0000000000..32cdeef1e9 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/INegotiateStateFactory.cs @@ -0,0 +1,11 @@ +// 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.Authentication.Negotiate +{ + // For testing + internal interface INegotiateStateFactory + { + INegotiateState CreateInstance(); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/NegotiateLoggingExtensions.cs b/src/Security/Authentication/Negotiate/src/Internal/NegotiateLoggingExtensions.cs new file mode 100644 index 0000000000..399b6fbe52 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/NegotiateLoggingExtensions.cs @@ -0,0 +1,71 @@ +// 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.Extensions.Logging +{ + internal static class NegotiateLoggingExtensions + { + private static Action _incompleteNegotiateChallenge; + private static Action _negotiateComplete; + private static Action _enablingCredentialPersistence; + private static Action _disablingCredentialPersistence; + private static Action _exceptionProcessingAuth; + private static Action _challengeNegotiate; + private static Action _reauthenticating; + + static NegotiateLoggingExtensions() + { + _incompleteNegotiateChallenge = LoggerMessage.Define( + eventId: new EventId(1, "IncompleteNegotiateChallenge"), + logLevel: LogLevel.Information, + formatString: "Incomplete Negotiate handshake, sending an additional 401 Negotiate challenge."); + _negotiateComplete = LoggerMessage.Define( + eventId: new EventId(2, "NegotiateComplete"), + logLevel: LogLevel.Debug, + formatString: "Completed Negotiate authentication."); + _enablingCredentialPersistence = LoggerMessage.Define( + eventId: new EventId(3, "EnablingCredentialPersistence"), + logLevel: LogLevel.Debug, + formatString: "Enabling credential persistence for a complete Kerberos handshake."); + _disablingCredentialPersistence = LoggerMessage.Define( + eventId: new EventId(4, "DisablingCredentialPersistence"), + logLevel: LogLevel.Debug, + formatString: "Disabling credential persistence for a complete {protocol} handshake."); + _exceptionProcessingAuth = LoggerMessage.Define( + eventId: new EventId(5, "ExceptionProcessingAuth"), + logLevel: LogLevel.Error, + formatString: "An exception occurred while processing the authentication request."); + _challengeNegotiate = LoggerMessage.Define( + eventId: new EventId(6, "ChallengeNegotiate"), + logLevel: LogLevel.Debug, + formatString: "Challenged 401 Negotiate"); + _reauthenticating = LoggerMessage.Define( + eventId: new EventId(7, "Reauthenticating"), + logLevel: LogLevel.Debug, + formatString: "Negotiate data received for an already authenticated connection, Re-authenticating."); + } + + public static void IncompleteNegotiateChallenge(this ILogger logger) + => _incompleteNegotiateChallenge(logger, null); + + public static void NegotiateComplete(this ILogger logger) + => _negotiateComplete(logger, null); + + public static void EnablingCredentialPersistence(this ILogger logger) + => _enablingCredentialPersistence(logger, null); + + public static void DisablingCredentialPersistence(this ILogger logger, string protocol) + => _disablingCredentialPersistence(logger, protocol, null); + + public static void ExceptionProcessingAuth(this ILogger logger, Exception ex) + => _exceptionProcessingAuth(logger, ex); + + public static void ChallengeNegotiate(this ILogger logger) + => _challengeNegotiate(logger, null); + + public static void Reauthenticating(this ILogger logger) + => _reauthenticating(logger, null); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs new file mode 100644 index 0000000000..37a1dff6f0 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateState.cs @@ -0,0 +1,96 @@ +// 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.Linq; +using System.Net; +using System.Reflection; +using System.Security.Authentication; +using System.Security.Principal; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + internal class ReflectedNegotiateState : INegotiateState + { + private static readonly ConstructorInfo _constructor; + private static readonly MethodInfo _getOutgoingBlob; + private static readonly MethodInfo _isCompleted; + private static readonly MethodInfo _protocol; + private static readonly MethodInfo _getIdentity; + private static readonly MethodInfo _closeContext; + + private readonly object _instance; + + static ReflectedNegotiateState() + { + var ntAuthType = typeof(AuthenticationException).Assembly.GetType("System.Net.NTAuthentication"); + _constructor = ntAuthType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First(); + _getOutgoingBlob = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => + info.Name.Equals("GetOutgoingBlob") && info.GetParameters().Count() == 2).Single(); + _isCompleted = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => + info.Name.Equals("get_IsCompleted")).Single(); + _protocol = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => + info.Name.Equals("get_ProtocolName")).Single(); + _closeContext = ntAuthType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(info => + info.Name.Equals("CloseContext")).Single(); + + var negoStreamPalType = typeof(AuthenticationException).Assembly.GetType("System.Net.Security.NegotiateStreamPal"); + _getIdentity = negoStreamPalType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(info => + info.Name.Equals("GetIdentity")).Single(); + } + + public ReflectedNegotiateState() + { + // internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding) + var credential = CredentialCache.DefaultCredentials; + _instance = _constructor.Invoke(new object[] { true, "Negotiate", credential, null, 0, null }); + } + + // Copied rather than reflected to remove the IsCompleted -> CloseContext check. + // The client doesn't need the context once auth is complete, but the server does. + // I'm not sure why it auto-closes for the client given that the client closes it just a few lines later. + // https://github.com/dotnet/corefx/blob/a3ab91e10045bb298f48c1d1f9bd5b0782a8ac46/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs#L134 + public string GetOutgoingBlob(string incomingBlob) + { + byte[] decodedIncomingBlob = null; + if (incomingBlob != null && incomingBlob.Length > 0) + { + decodedIncomingBlob = Convert.FromBase64String(incomingBlob); + } + byte[] decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, true); + + string outgoingBlob = null; + if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) + { + outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); + } + + return outgoingBlob; + } + + private byte[] GetOutgoingBlob(byte[] incomingBlob, bool thrownOnError) + { + return (byte[])_getOutgoingBlob.Invoke(_instance, new object[] { incomingBlob, thrownOnError }); + } + + public bool IsCompleted + { + get => (bool)_isCompleted.Invoke(_instance, Array.Empty()); + } + + public string Protocol + { + get => (string)_protocol.Invoke(_instance, Array.Empty()); + } + + public IIdentity GetIdentity() + { + return (IIdentity)_getIdentity.Invoke(obj: null, parameters: new object[] { _instance }); + } + + public void Dispose() + { + _closeContext.Invoke(_instance, Array.Empty()); + } + } +} diff --git a/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs new file mode 100644 index 0000000000..31fa29d49b --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Internal/ReflectedNegotiateStateFactory.cs @@ -0,0 +1,17 @@ +// 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.Text; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + internal class ReflectedNegotiateStateFactory : INegotiateStateFactory + { + public INegotiateState CreateInstance() + { + return new ReflectedNegotiateState(); + } + } +} diff --git a/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj new file mode 100644 index 0000000000..f71af581a9 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Microsoft.AspNetCore.Authentication.Negotiate.csproj @@ -0,0 +1,16 @@ + + + + ASP.NET Core authentication handler used to authenticate requests using Negotiate, Kerberos, or NTLM. + netcoreapp3.0 + true + aspnetcore;authentication;security + true + + + + + + + + diff --git a/src/Security/Authentication/Negotiate/src/NegotiateDefaults.cs b/src/Security/Authentication/Negotiate/src/NegotiateDefaults.cs new file mode 100644 index 0000000000..1c119ea871 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/NegotiateDefaults.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.Authentication.Negotiate +{ + /// + /// Default values used by Negotiate authentication. + /// + public static class NegotiateDefaults + { + /// + /// Default value for AuthenticationScheme used to identify the Negotiate auth handler. + /// + public const string AuthenticationScheme = "Negotiate"; + } +} diff --git a/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs new file mode 100644 index 0000000000..ea01f040a8 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/NegotiateExtensions.cs @@ -0,0 +1,62 @@ +// 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.Authentication; +using Microsoft.AspNetCore.Authentication.Negotiate; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions for enabling Negotiate authentication. + /// + public static class NegotiateExtensions + { + /// + /// Adds Negotiate authentication. + /// + /// The . + /// The original builder. + public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder) + => builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, _ => { }); + + /// + /// Adds and configures Negotiate authentication. + /// + /// The . + /// Allows for configuring the authentication handler. + /// The original builder. + public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddNegotiate(NegotiateDefaults.AuthenticationScheme, configureOptions); + + /// + /// Adds and configures Negotiate authentication. + /// + /// The . + /// The scheme name used to identify the authentication handler internally. + /// Allows for configuring the authentication handler. + /// The original builder. + public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddNegotiate(authenticationScheme, displayName: null, configureOptions: configureOptions); + + /// + /// Adds and configures Negotiate authentication. + /// + /// The . + /// The scheme name used to identify the authentication handler internally. + /// The name displayed to users when selecting an authentication handler. The default is null to prevent this from displaying. + /// Allows for configuring the authentication handler. + /// The original builder. + public static AuthenticationBuilder AddNegotiate(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable($"ASPNETCORE_TOKEN"))) + { + throw new NotSupportedException( + "The Negotiate authentication handler must not be used with IIS out-of-process mode or similar reverse proxies that share connections between users." + + " Use the Windows Authentication features available within IIS or IIS Express."); + } + + return builder.AddScheme(authenticationScheme, displayName, configureOptions); + } + } +} diff --git a/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs new file mode 100644 index 0000000000..27844382fb --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/NegotiateHandler.cs @@ -0,0 +1,340 @@ +// 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.Diagnostics; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + /// + /// Authenticates requests using Negotiate, Kerberos, or NTLM. + /// + public class NegotiateHandler : AuthenticationHandler, IAuthenticationRequestHandler + { + private const string AuthPersistenceKey = nameof(AuthPersistence); + private const string NegotiateVerb = "Negotiate"; + private const string AuthHeaderPrefix = NegotiateVerb + " "; + + private bool _requestProcessed; + private INegotiateState _negotiateState; + + /// + /// Creates a new + /// + /// + /// + /// + /// + public NegotiateHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) + : base(options, logger, encoder, clock) + { } + + /// + /// The handler calls methods on the events which give the application control at certain points where processing is occurring. + /// If it is not provided a default instance is supplied which does nothing when the methods are called. + /// + protected new NegotiateEvents Events + { + get => (NegotiateEvents)base.Events; + set => base.Events = value; + } + + /// + /// Creates the default events type. + /// + /// + protected override Task CreateEventsAsync() => Task.FromResult(new NegotiateEvents()); + + private bool IsHttp2 => string.Equals("HTTP/2", Request.Protocol, StringComparison.OrdinalIgnoreCase); + + /// + /// Intercepts incomplete Negotiate authentication handshakes and continues or completes them. + /// + /// True if a response was generated, false otherwise. + public async Task HandleRequestAsync() + { + try + { + if (_requestProcessed) + { + // This request was already processed but something is re-executing it like an exception handler. + // Don't re-run because we could corrupt the connection state, e.g. if this was a stage2 NTLM request + // that we've already completed the handshake for. + return false; + } + + _requestProcessed = true; + + if (IsHttp2) + { + // HTTP/2 is not supported. Do not throw because this may be running on a server that supports + // both HTTP/1 and HTTP/2. + return false; + } + + var connectionItems = GetConnectionItems(); + var persistence = (AuthPersistence)connectionItems[AuthPersistenceKey]; + _negotiateState = persistence?.State; + + var authorizationHeader = Request.Headers[HeaderNames.Authorization]; + + if (StringValues.IsNullOrEmpty(authorizationHeader)) + { + if (_negotiateState?.IsCompleted == false) + { + throw new InvalidOperationException("An anonymous request was received in between authentication handshake requests."); + } + return false; + } + + var authorization = authorizationHeader.ToString(); + string token = null; + if (authorization.StartsWith(AuthHeaderPrefix, StringComparison.OrdinalIgnoreCase)) + { + token = authorization.Substring(AuthHeaderPrefix.Length).Trim(); + } + else + { + if (_negotiateState?.IsCompleted == false) + { + throw new InvalidOperationException("Non-negotiate request was received in between authentication handshake requests."); + } + return false; + } + + // WinHttpHandler re-authenticates an existing connection if it gets another challenge on subsequent requests. + if (_negotiateState?.IsCompleted == true) + { + Logger.Reauthenticating(); + _negotiateState.Dispose(); + _negotiateState = null; + persistence.State = null; + } + + _negotiateState ??= Options.StateFactory.CreateInstance(); + + var outgoing = _negotiateState.GetOutgoingBlob(token); + + if (!_negotiateState.IsCompleted) + { + persistence ??= EstablishConnectionPersistence(connectionItems); + // Save the state long enough to complete the multi-stage handshake. + // We'll remove it once complete if !PersistNtlm/KerberosCredentials. + persistence.State = _negotiateState; + + Logger.IncompleteNegotiateChallenge(); + Response.StatusCode = StatusCodes.Status401Unauthorized; + Response.Headers.Append(HeaderNames.WWWAuthenticate, AuthHeaderPrefix + outgoing); + return true; + } + + Logger.NegotiateComplete(); + + // There can be a final blob of data we need to send to the client, but let the request execute as normal. + if (!string.IsNullOrEmpty(outgoing)) + { + Response.OnStarting(() => + { + // Only include it if the response ultimately succeeds. This avoids adding it twice if Challenge is called again. + if (Response.StatusCode < StatusCodes.Status400BadRequest) + { + Response.Headers.Append(HeaderNames.WWWAuthenticate, AuthHeaderPrefix + outgoing); + } + return Task.CompletedTask; + }); + } + + // Deal with connection credential persistence. + + if (_negotiateState.Protocol == "NTLM" && !Options.PersistNtlmCredentials) + { + // NTLM was already put in the persitence cache on the prior request so we could complete the handshake. + // Take it out if we don't want it to persist. + Debug.Assert(object.ReferenceEquals(persistence?.State, _negotiateState), + "NTLM is a two stage process, it must have already been in the cache for the handshake to succeed."); + Logger.DisablingCredentialPersistence(_negotiateState.Protocol); + persistence.State = null; + Response.RegisterForDispose(_negotiateState); + } + else if (_negotiateState.Protocol == "Kerberos") + { + // Kerberos can require one or two stage handshakes + if (Options.PersistKerberosCredentials) + { + Logger.EnablingCredentialPersistence(); + persistence ??= EstablishConnectionPersistence(connectionItems); + persistence.State = _negotiateState; + } + else + { + if (persistence?.State != null) + { + Logger.DisablingCredentialPersistence(_negotiateState.Protocol); + persistence.State = null; + } + Response.RegisterForDispose(_negotiateState); + } + } + + // Note we run the Authenticated event in HandleAuthenticateAsync so it is per-request rather than per connection. + } + catch (Exception ex) + { + Logger.ExceptionProcessingAuth(ex); + var errorContext = new AuthenticationFailedContext(Context, Scheme, Options) { Exception = ex }; + await Events.AuthenticationFailed(errorContext); + + if (errorContext.Result != null) + { + if (errorContext.Result.Handled) + { + return true; + } + else if (errorContext.Result.Skipped) + { + return false; + } + else if (errorContext.Result.Failure != null) + { + throw new Exception("An error was returned from the AuthenticationFailed event.", errorContext.Result.Failure); + } + } + + throw; + } + + return false; + } + + /// + /// Checks if the current request is authenticated and returns the user. + /// + /// + protected override async Task HandleAuthenticateAsync() + { + if (!_requestProcessed) + { + throw new InvalidOperationException("AuthenticateAsync must not be called before the UseAuthentication middleware runs."); + } + + if (IsHttp2) + { + // Not supported. We don't throw because Negotiate may be set as the default auth + // handler on a server that's running HTTP/1 and HTTP/2. We'll challenge HTTP/2 requests + // that require auth and they'll downgrade to HTTP/1.1. + return AuthenticateResult.NoResult(); + } + + if (_negotiateState == null) + { + return AuthenticateResult.NoResult(); + } + + if (!_negotiateState.IsCompleted) + { + // This case should have been rejected by HandleRequestAsync + throw new InvalidOperationException("Attempting to use an incomplete authentication context."); + } + + // Make a new copy of the user for each request, they are mutable objects and + // things like ClaimsTransformation run per request. + var identity = _negotiateState.GetIdentity(); + ClaimsPrincipal user; + if (identity is WindowsIdentity winIdentity) + { + user = new WindowsPrincipal(winIdentity); + Response.RegisterForDispose(winIdentity); + } + else + { + user = new ClaimsPrincipal(new ClaimsIdentity(identity)); + } + + var authenticatedContext = new AuthenticatedContext(Context, Scheme, Options) + { + Principal = user + }; + await Events.Authenticated(authenticatedContext); + + if (authenticatedContext.Result != null) + { + return authenticatedContext.Result; + } + + var ticket = new AuthenticationTicket(authenticatedContext.Principal, authenticatedContext.Properties, Scheme.Name); + return AuthenticateResult.Success(ticket); + } + + /// + /// Issues a 401 WWW-Authenticate Negotiate challenge. + /// + /// + /// + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + // We allow issuing a challenge from an HTTP/2 request. Browser clients will gracefully downgrade to HTTP/1.1. + // SocketHttpHandler will not downgrade (https://github.com/dotnet/corefx/issues/35195), but WinHttpHandler will. + var eventContext = new ChallengeContext(Context, Scheme, Options, properties); + await Events.Challenge(eventContext); + if (eventContext.Handled) + { + return; + } + + Response.StatusCode = StatusCodes.Status401Unauthorized; + Response.Headers.Append(HeaderNames.WWWAuthenticate, NegotiateVerb); + Logger.ChallengeNegotiate(); + } + + private AuthPersistence EstablishConnectionPersistence(IDictionary items) + { + Debug.Assert(!items.ContainsKey(AuthPersistenceKey), "This should only be registered once per connection"); + var persistence = new AuthPersistence(); + RegisterForConnectionDispose(persistence); + items[AuthPersistenceKey] = persistence; + return persistence; + } + + private IDictionary GetConnectionItems() + { + return Context.Features.Get()?.Items + ?? throw new NotSupportedException($"Negotiate authentication requires a server that supports {nameof(IConnectionItemsFeature)} like Kestrel."); + } + + private void RegisterForConnectionDispose(IDisposable authState) + { + var connectionCompleteFeature = Context.Features.Get() + ??throw new NotSupportedException($"Negotiate authentication requires a server that supports {nameof(IConnectionCompleteFeature)} like Kestrel."); + connectionCompleteFeature.OnCompleted(DisposeState, authState); + } + + private static Task DisposeState(object state) + { + ((IDisposable)state).Dispose(); + return Task.CompletedTask; + } + + // This allows us to have one disposal registration per connection and limits churn on the Items collection. + private class AuthPersistence : IDisposable + { + internal INegotiateState State { get; set; } + + public void Dispose() + { + State?.Dispose(); + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs new file mode 100644 index 0000000000..78df485897 --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/NegotiateOptions.cs @@ -0,0 +1,39 @@ +// 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.Authentication.Negotiate +{ + /// + /// Options class provides information needed to control Negotiate Authentication handler behavior + /// + public class NegotiateOptions : AuthenticationSchemeOptions + { + /// + /// The object provided by the application to process events raised by the negotiate authentication handler. + /// The application may use the existing NegotiateEvents instance and assign delegates only to the events it + /// wants to process. The application may also replace it with its own derived instance. + /// + public new NegotiateEvents Events + { + get { return (NegotiateEvents)base.Events; } + set { base.Events = value; } + } + + /// + /// Indicates if Kerberos credentials should be persisted and re-used for subsquent anonymous requests. + /// This option must not be used if connections may be shared by requests from different users. + /// The default is false. + /// + public bool PersistKerberosCredentials { get; set; } = false; + + /// + /// Indicates if NTLM credentials should be persisted and re-used for subsquent anonymous requests. + /// This option must not be used if connections may be shared by requests from different users. + /// The default is true. + /// + public bool PersistNtlmCredentials { get; set; } = true; + + // For testing + internal INegotiateStateFactory StateFactory { get; set; } = new ReflectedNegotiateStateFactory(); + } +} diff --git a/src/Security/Authentication/Negotiate/src/Properties/AssemblyInfo.cs b/src/Security/Authentication/Negotiate/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..626e2fd7ed --- /dev/null +++ b/src/Security/Authentication/Negotiate/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Authentication.Negotiate.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md new file mode 100644 index 0000000000..e263a2c5f7 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md @@ -0,0 +1,48 @@ +Cross Machine Tests + +Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also neccisary for interop testing across OSs. Kerberos also requires domain controler SPN configuration so we can't test it on arbitrary test boxes. + +Test structure: +- A remote test server with various endpoints with different authentication restrictions. +- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and desciption. +- The CrossMachineTest class that drives the tests. It invokes the client app with the theory data and confirms the results. + +We use these three components beceause it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. + +(Static) Environment Setup: +- Warning, this environment can take a day to set up. That's why we want a static test environment that we can re-use. +- Create a Windows server running DNS and Active Directory. Promote it to a domain controller. + - Create an SPN on this machine for Windows -> Windows testing. `setspn -S "http/chrross-dc.crkerberos.com" -U administrator` + - Future: Can we replace the domain controller with an AAD instance? We'd still want a second windows machine for Windows -> Windows testing, but AAD might be easier to configure. + - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-getting-started + - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm + - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-enable-kcd +- Create another Windows machine and join it to the test domain. +- Create a Linux machine and joing it to the domain. Ubuntu 18.04 has been used in the past. + - https://www.safesquid.com/content-filtering/integrating-linux-host-windows-ad-kerberos-sso-authentication + - Include an HTTP SPN + +Test deployment variations, prioritized: +- Windows -> Linux +- Windows -> Windows +- Localhost Windows -> Windows +Future: +- Note the Linux HttpClient doesn't support default credentials, you have to update Negotiate.Client to provide explicit credentials. +- Linux -> Windows +- Linux -> Linux +- Localhost Linux -> Linux + +Test run setup: +- Publish Negotiate.Client as a standalone application targeting the OS you want to run it on. Copy it to that machine and run it. + - Make sure it's running on a public IP and that the port is not blocked by the firewall. + - Note we primarily care about having the server on the latest runtime. Publishing the client the same way is convenient but not required. We do want to update it periodically for interop testing. + - HTTPS is optional for this client. +- Publish Negotiate.Server as a standalone application targeting the OS you want to run it on. Copy it to that machine and run it. + - Make sure it's running on a public IP and that the port is not blocked by the firewall. + - Note the server app starts two server instances, one with connection persistence enabled and the other with it disabled. + - HTTPS is needed on the server for some HTTP/2 downgrade tests. These tests can be ignored if HTTPS is not conveniently available. + - Future: Automate remote publishing +- In CrossMachineTests: + - Set ClientAddress, ServerPersistAddress, and ServerNonPersistAddress. (Future: Pull from environment variables?) + - UnSkip the test cases. (Future: Make these conditional on the test environment being available, environment variables?) +- Run tests! diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineTests.cs new file mode 100644 index 0000000000..0bfed42c03 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineTests.cs @@ -0,0 +1,150 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + // The OS's being tested are on other machines, don't duplicate the tests across runs. + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] + // See CrossMachineReadMe.md + public class CrossMachineTests + { + private const string Http11Version = "HTTP/1.1"; + private const string Http2Version = "HTTP/2"; + + private const string ClientAddress = + // "http://chrross-udesk:5004"; + "https://localhost:5005"; + private const string ServerName = + "chrross-dc"; + // "chrross-udesk"; + private static readonly string ServerPersistAddress = $"http://{ServerName}.CRKerberos.com:5000"; + private static readonly string ServerNonPersistAddress = $"http://{ServerName}.CRKerberos.com:5002"; + + public static IEnumerable Http11And2 => + new List + { + new object[] { Http11Version }, + new object[] { Http2Version }, + }; + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(Http11And2))] + public Task Anonymous_NoChallenge_NoOps(string protocol) + { + return RunTest(protocol, "/Anonymous/Unrestricted"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(Http11And2))] + public Task Anonymous_Challenge_401Negotiate(string protocol) + { + return RunTest(protocol, "/Anonymous/Authorized"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(Http11And2))] + public Task DefautCredentials_Success(string protocol) + { + return RunTest(protocol, "/DefaultCredentials/Authorized"); + } + + public static IEnumerable HttpOrders => + new List + { + new object[] { Http11Version, Http11Version }, + new object[] { Http11Version, Http2Version }, + new object[] { Http2Version, Http11Version }, + }; + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(HttpOrders))] + // AuthorizedRequestAfterAuth_ReUses1WithPersistence would give the same results + public Task UrestrictedRequestAfterAuth_ReUses1WithPersistence(string protocol1, string protocol2) + { + return RunTest(ServerPersistAddress, protocol1, protocol2, "/AfterAuth/Unrestricted/Persist"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(HttpOrders))] + public Task UrestrictedRequestAfterAuth_AnonymousWhenNotPersisted(string protocol1, string protocol2) + { + return RunTest(ServerNonPersistAddress, protocol1, protocol2, "/AfterAuth/Unrestricted/NonPersist"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(HttpOrders))] + public Task AuthorizedRequestAfterAuth_ReauthenticatesWhenNotPersisted(string protocol1, string protocol2) + { + return RunTest(ServerNonPersistAddress, protocol1, protocol2, "/AfterAuth/Authorized/NonPersist"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(Http11And2))] + public Task Unauthorized_401Negotiate(string protocol) + { + return RunTest(protocol, "/Unauthorized"); + } + + [ConditionalTheory(Skip = "Manual testing only")] + [MemberData(nameof(Http11And2))] + public Task UnauthorizedAfterAuthenticated_Success(string protocol) + { + return RunTest(protocol, "/AfterAuth/Unauthorized", persist: true); + } + + private Task RunTest(string protocol, string path, bool persist = false) + { + var queryBuilder = new QueryBuilder + { + { "server", persist ? ServerPersistAddress : ServerNonPersistAddress }, + { "protocol", protocol } + }; + + return RunTest(path, queryBuilder); + } + + private Task RunTest(string server, string protocol1, string protocol2, string path) + { + var queryBuilder = new QueryBuilder + { + { "server", server }, + { "protocol1", protocol1 }, + { "protocol2", protocol2 } + }; + + return RunTest(path, queryBuilder); + } + + private async Task RunTest(string path, QueryBuilder queryBuilder) + { + using var client = CreateClient(ClientAddress); + + var response = await client.GetAsync("/authtest" + path + queryBuilder.ToString()); + var body = await response.Content.ReadAsStringAsync(); + + Assert.True(HttpStatusCode.OK == response.StatusCode, $"{response.StatusCode}: {body}"); + } + + private static HttpClient CreateClient(string address) + { + return new HttpClient(new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }) + { + BaseAddress = new Uri(address), + DefaultRequestVersion = new Version(2, 0), + }; + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/Microsoft.AspNetCore.Authentication.Negotiate.FunctionalTest.csproj b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/Microsoft.AspNetCore.Authentication.Negotiate.FunctionalTest.csproj new file mode 100644 index 0000000000..24c387964f --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/Microsoft.AspNetCore.Authentication.Negotiate.FunctionalTest.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.0 + true + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/NegotiateHandlerFunctionalTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/NegotiateHandlerFunctionalTests.cs new file mode 100644 index 0000000000..742bb426b0 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/NegotiateHandlerFunctionalTests.cs @@ -0,0 +1,307 @@ +// 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.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + // In theory this would work on Linux and Mac, but the client would require explicit credentials. + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public class NegotiateHandlerFunctionalTests + { + private static readonly Version Http11Version = new Version(1, 1); + private static readonly Version Http2Version = new Version(2, 0); + + public static IEnumerable Http11And2 => + new List + { + new object[] { Http11Version }, + new object[] { Http2Version }, + }; + + [ConditionalTheory] + [MemberData(nameof(Http11And2))] + public async Task Anonymous_NoChallenge_NoOps(Version version) + { + using var host = await CreateHostAsync(); + using var client = CreateSocketHttpClient(host); + client.DefaultRequestVersion = version; + + var result = await client.GetAsync("/Anonymous" + version.Major); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.False(result.Headers.Contains(HeaderNames.WWWAuthenticate)); + Assert.Equal(version, result.Version); + } + + [ConditionalTheory] + [MemberData(nameof(Http11And2))] + public async Task Anonymous_Challenge_401Negotiate(Version version) + { + using var host = await CreateHostAsync(); + // WinHttpHandler can't disable default credentials on localhost, use SocketHttpHandler. + using var client = CreateSocketHttpClient(host); + client.DefaultRequestVersion = version; + + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); + Assert.Equal("Negotiate", result.Headers.WwwAuthenticate.ToString()); + Assert.Equal(version, result.Version); + } + + [ConditionalTheory] + [MemberData(nameof(Http11And2))] + public async Task DefautCredentials_Success(Version version) + { + using var host = await CreateHostAsync(); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = version; + + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); // HTTP/2 downgrades. + } + + public static IEnumerable HttpOrders => + new List + { + new object[] { Http11Version, Http11Version }, + new object[] { Http11Version, Http2Version }, + new object[] { Http2Version, Http11Version }, + }; + + [ConditionalTheory] + [MemberData(nameof(HttpOrders))] + public async Task RequestAfterAuth_ReUses1WithPersistence(Version first, Version second) + { + using var host = await CreateHostAsync(options => options.PersistNtlmCredentials = true); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = first; + + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); // Http/2 downgrades + + // Re-uses the 1.1 connection. + result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/AlreadyAuthenticated") { Version = second }); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); + } + + [ConditionalTheory] + [MemberData(nameof(HttpOrders))] + public async Task RequestAfterAuth_ReauthenticatesWhenNotPersisted(Version first, Version second) + { + using var host = await CreateHostAsync(options => options.PersistNtlmCredentials = false); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = first; + + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); // Http/2 downgrades + + // Re-uses the 1.1 connection. + result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/Authenticate") { Version = second }); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public async Task RequestAfterAuth_Http2Then2_Success(bool persistNtlm) + { + using var host = await CreateHostAsync(options => options.PersistNtlmCredentials = persistNtlm); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = Http2Version; + + // Falls back to HTTP/1.1 after trying HTTP/2. + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); + + // Tries HTTP/2, falls back to HTTP/1.1 and re-authenticates. + result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public async Task RequestAfterAuth_Http2Then2Anonymous_Success(bool persistNtlm) + { + using var host = await CreateHostAsync(options => options.PersistNtlmCredentials = persistNtlm); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = Http2Version; + + // Falls back to HTTP/1.1 after trying HTTP/2. + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); + + // Makes an anonymous HTTP/2 request + result = await client.GetAsync("/Anonymous2"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http2Version, result.Version); + } + + [ConditionalTheory] + [MemberData(nameof(Http11And2))] + public async Task Unauthorized_401Negotiate(Version version) + { + using var host = await CreateHostAsync(); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = version; + + var result = await client.GetAsync("/Unauthorized"); + Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); + Assert.Equal("Negotiate", result.Headers.WwwAuthenticate.ToString()); + Assert.Equal(Http11Version, result.Version); // HTTP/2 downgrades. + } + + [ConditionalTheory] + [MemberData(nameof(Http11And2))] + public async Task UnauthorizedAfterAuthenticated_Success(Version version) + { + using var host = await CreateHostAsync(); + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade. WinHttpHandler does. + using var client = CreateWinHttpClient(host); + client.DefaultRequestVersion = version; + + var result = await client.GetAsync("/Authenticate"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(Http11Version, result.Version); // HTTP/2 downgrades. + + result = await client.GetAsync("/Unauthorized"); + Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); + Assert.Equal("Negotiate", result.Headers.WwwAuthenticate.ToString()); + Assert.Equal(Http11Version, result.Version); // HTTP/2 downgrades. + } + + private static Task CreateHostAsync(Action configureOptions = null) + { + var builder = new HostBuilder() + .ConfigureServices(services => services + .AddRouting() + .AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(configureOptions)) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(options => + { + options.Listen(IPAddress.Loopback, 0, endpoint => + { + endpoint.UseHttps("testCert.pfx", "testPassword"); + }); + }); + webHostBuilder.Configure(app => + { + app.UseRouting(); + app.UseAuthentication(); + app.UseEndpoints(ConfigureEndpoints); + }); + }); + + return builder.StartAsync(); + } + + private static void ConfigureEndpoints(IEndpointRouteBuilder builder) + { + builder.Map("/Anonymous1", context => + { + Assert.Equal("HTTP/1.1", context.Request.Protocol); + Assert.False(context.User.Identity.IsAuthenticated, "Anonymous"); + return Task.CompletedTask; + }); + + builder.Map("/Anonymous2", context => + { + Assert.Equal("HTTP/2", context.Request.Protocol); + Assert.False(context.User.Identity.IsAuthenticated, "Anonymous"); + return Task.CompletedTask; + }); + + builder.Map("/Authenticate", async context => + { + if (!context.User.Identity.IsAuthenticated) + { + await context.ChallengeAsync(); + return; + } + + Assert.Equal("HTTP/1.1", context.Request.Protocol); // Not HTTP/2 + var name = context.User.Identity.Name; + Assert.False(string.IsNullOrEmpty(name), "name"); + await context.Response.WriteAsync(name); + }); + + builder.Map("/AlreadyAuthenticated", async context => + { + Assert.Equal("HTTP/1.1", context.Request.Protocol); // Not HTTP/2 + Assert.True(context.User.Identity.IsAuthenticated, "Authenticated"); + var name = context.User.Identity.Name; + Assert.False(string.IsNullOrEmpty(name), "name"); + await context.Response.WriteAsync(name); + }); + + builder.Map("/Unauthorized", async context => + { + // Simulate Authorization failure + var result = await context.AuthenticateAsync(); + await context.ChallengeAsync(); + }); + } + + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade. WinHttpHandler does. + private static HttpClient CreateWinHttpClient(IHost host) + { + var address = host.Services.GetRequiredService().Features.Get().Addresses.First(); + + // WinHttpHandler always uses default credentials on localhost + return new HttpClient(new WinHttpHandler() + { + ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }) + { + BaseAddress = new Uri(address) + }; + } + + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade. WinHttpHandler does. + private static HttpClient CreateSocketHttpClient(IHost host, bool useDefaultCredentials = false) + { + var address = host.Services.GetRequiredService().Features.Get().Addresses.First(); + + return new HttpClient(new HttpClientHandler() + { + UseDefaultCredentials = useDefaultCredentials, + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }) + { + BaseAddress = new Uri(address) + }; + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/testCert.pfx b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/testCert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..888ccb032a97ac6d36afdf569ea5a7ddb15d1111 GIT binary patch literal 1403 zcmXqLVl8K4WHxAGHD=?~YV&CO&dbQoxS)wug{6sA0VphE(8Tx`MT+qqOB3TOpzt#w ze#FKN)y2cb$h4sGk3r*40~t0hm>|$-J{FGUht3{YZd@71#KeAqrSZm11(&cnPAfwl zX1aFU|ILVKRdSQy- z2e!P4PsNH>x|lM#?2tN^u!OtsfyA#vHJ9&i(-)s?)TWnwcKOj)d$0B8{MztM`n%q| z{M9mBORWq;!#mbWatr7EcMEIZ*4a?}=x6n>tpRI(B{{?~YMHHDZ=^4pw$LMjck7I! zzvaINOS^^Uruf~=&E8?7V|LlUW|HCQxevMbf3gl*&Zjy5$@e5btT;st*;rT@85kH0ViAHOh5>SZ3?&Sy47v;k43-Qj4CX-E zj3J334JeYzV8UR^U<_oLFc>i;G9&|8mO$DVES3mlr2y5X0c9WqN8QWz3J#%r@N zvM@H83o?R@_hrZgYAs^OXD9~ZG@x|~48c%VInarMD*}qAgT({D zI?I4&qyYIv22IT3aQCybEofrq01B}%F)|o5F(D@n%y|D%?IOCx)9)xK-W!^jE<{h@ zzRS8(=PhHipURGk)u%p0dmpSmyuxYW{=ZK;@@v;tW*6QvI&pvUzn#nCmEG+(*OaEY zHxyaA$}FzmBM{?rk!9XSmC#1RXUU6B9DMJ%;BbQNIu(-%91(Bi1(NdJtYiL`@IGhA zymKkomSs`&Uz4m!k3&o!JUAh?!Q#QD&0np^X~?&+OfDO}h40Y?O!j zm+pF&LXT;0D;CUJ{}3h zV=+TtLHRF*V;_PYSBA>=_B;HRT5ZqPx~O3B#ecWfUQ`N+{Z zHz=;g;q`anh}wB4zjfZ3E4-jitMBI#j_b+4d0NjkdpS5x{qcHna;er`w{2xzHP5Zr zD&3qSZl%|@=-sy&?CeTA)jc}0p9EZ5|Fin@5x#gvcCPO70Noeu z@wbEw&a=P1_dH|K#j4X&B#)n*m$*J9S&DJz`WYV`w#1pbC5ZgotXi%3+&@ji$iH2g zv4S=Abj$Qi?LQ|je3Ll1)vhhMB<0@21zEE$lmu>lRDDD5pW^)NA7`h3_PLk6;-sH% zm%OvjeamWTnc45;N{7w|~ZryW7oH-#!yqnq&3MUig`wfZDsXX`GjNc&tH zTox#|;gN~NB(75l+b115bTNbVf=qjk$Bf-)P0FshsX8@Gx+^DLdwhjTxV_?1HaFF6 zk|m#OWi>>ab5q>se9O>3d-2GAkLS@>eRgCh7+4#~8?dvnYV$EONwJ8uv-NfrpVt4B r?jsp}XX2cX|BhL`WD&_^IHf-$@%m29k1q?q&7B#jKP_$tC_?}MhAKx! literal 0 HcmV?d00001 diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs new file mode 100644 index 0000000000..adc562d0dd --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/EventTests.cs @@ -0,0 +1,393 @@ +// 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.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + public class EventTests + { + [Fact] + public async Task OnChallenge_Fires() + { + var eventInvoked = false; + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnChallenge = context => + { + // Not changed yet + eventInvoked = true; + Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); + Assert.False(context.Response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)); + return Task.CompletedTask; + } + }; + }); + + var result = await SendAsync(server, "/Authenticate", new TestConnection()); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + Assert.True(eventInvoked); + } + + [Fact] + public async Task OnChallenge_Handled() + { + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnChallenge = context => + { + context.Response.StatusCode = StatusCodes.Status418ImATeapot; + context.Response.Headers[HeaderNames.WWWAuthenticate] = "Teapot"; + context.HandleResponse(); + return Task.CompletedTask; + } + }; + }); + + var result = await SendAsync(server, "/Authenticate", new TestConnection()); + Assert.Equal(StatusCodes.Status418ImATeapot, result.Response.StatusCode); + Assert.Equal("Teapot", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Fact] + public async Task OnAuthenticationFailed_Fires() + { + var eventInvoked = false; + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnAuthenticationFailed = context => + { + eventInvoked = true; + Assert.IsType(context.Exception); + Assert.Equal("InvalidBlob", context.Exception.Message); + return Task.CompletedTask; + } + }; + }); + + var ex = await Assert.ThrowsAsync(() => + SendAsync(server, "/404", new TestConnection(), "Negotiate InvalidBlob")); + Assert.Equal("InvalidBlob", ex.Message); + Assert.True(eventInvoked); + } + + [Fact] + public async Task OnAuthenticationFailed_Handled() + { + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnAuthenticationFailed = context => + { + context.Response.StatusCode = StatusCodes.Status418ImATeapot; ; + context.Response.Headers[HeaderNames.WWWAuthenticate] = "Teapot"; + context.HandleResponse(); + return Task.CompletedTask; + } + }; + }); + + var result = await SendAsync(server, "/404", new TestConnection(), "Negotiate InvalidBlob"); + Assert.Equal(StatusCodes.Status418ImATeapot, result.Response.StatusCode); + Assert.Equal("Teapot", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Fact] + public async Task OnAuthenticated_FiresOncePerRequest() + { + var callCount = 0; + var server = await CreateServerAsync(options => + { + options.PersistKerberosCredentials = true; + options.Events = new NegotiateEvents() + { + OnAuthenticated = context => + { + var identity = context.Principal.Identity; + Assert.True(identity.IsAuthenticated); + Assert.Equal("name", identity.Name); + Assert.Equal("Kerberos", identity.AuthenticationType); + callCount++; + return Task.CompletedTask; + } + }; + }); + + var testConnection = new TestConnection(); + await KerberosStage1And2Auth(server, testConnection); + var result = await SendAsync(server, "/Authenticate", testConnection); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.False(result.Response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)); + Assert.Equal(2, callCount); + } + + [Fact] + public async Task OnAuthenticated_Success_Continues() + { + var callCount = 0; + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnAuthenticated = context => + { + context.Success(); + callCount++; + return Task.CompletedTask; + } + }; + }); + + await KerberosStage1And2Auth(server, new TestConnection()); + Assert.Equal(1, callCount); + } + + [Fact] + public async Task OnAuthenticated_NoResult_SuppresesCredentials() + { + var callCount = 0; + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnAuthenticated = context => + { + context.NoResult(); + callCount++; + return Task.CompletedTask; + } + }; + }); + + var result = await SendAsync(server, "/Authenticate", new TestConnection(), "Negotiate ClientKerberosBlob"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + Assert.Equal(1, callCount); + } + + [Fact] + public async Task OnAuthenticated_Fail_SuppresesCredentials() + { + var callCount = 0; + var server = await CreateServerAsync(options => + { + options.Events = new NegotiateEvents() + { + OnAuthenticated = context => + { + callCount++; + context.Fail("Event error."); + return Task.CompletedTask; + } + }; + }); + + var result = await SendAsync(server, "/Authenticate", new TestConnection(), "Negotiate ClientKerberosBlob"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + Assert.Equal(1, callCount); + } + + private static async Task KerberosStage1And2Auth(TestServer server, TestConnection testConnection) + { + await KerberosStage1Auth(server, testConnection); + await KerberosStage2Auth(server, testConnection); + } + + private static async Task KerberosStage1Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientKerberosBlob1"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate ServerKerberosBlob1", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task KerberosStage2Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientKerberosBlob2"); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.Equal("Negotiate ServerKerberosBlob2", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task CreateServerAsync(Action configureOptions = null) + { + var builder = new HostBuilder() + .ConfigureServices(services => services + .AddRouting() + .AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(options => + { + options.StateFactory = new TestNegotiateStateFactory(); + configureOptions?.Invoke(options); + })) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseTestServer(); + webHostBuilder.Configure(app => + { + app.UseRouting(); + app.UseAuthentication(); + app.UseEndpoints(ConfigureEndpoints); + }); + }); + + var server = (await builder.StartAsync()).GetTestServer(); + return server; + } + + private static void ConfigureEndpoints(IEndpointRouteBuilder builder) + { + builder.Map("/Authenticate", async context => + { + if (!context.User.Identity.IsAuthenticated) + { + await context.ChallengeAsync(); + return; + } + + Assert.Equal("HTTP/1.1", context.Request.Protocol); // Not HTTP/2 + var name = context.User.Identity.Name; + Assert.False(string.IsNullOrEmpty(name), "name"); + await context.Response.WriteAsync(name); + }); + } + + private static Task SendAsync(TestServer server, string path, TestConnection connection, string authorizationHeader = null) + { + return server.SendAsync(context => + { + context.Request.Path = path; + if (!string.IsNullOrEmpty(authorizationHeader)) + { + context.Request.Headers[HeaderNames.Authorization] = authorizationHeader; + } + if (connection != null) + { + context.Features.Set(connection); + context.Features.Set(connection); + } + }); + } + + private class TestConnection : IConnectionItemsFeature, IConnectionCompleteFeature + { + public IDictionary Items { get; set; } = new ConnectionItems(); + + public void OnCompleted(Func callback, object state) + { + } + } + + private class TestNegotiateStateFactory : INegotiateStateFactory + { + public INegotiateState CreateInstance() => new TestNegotiateState(); + } + + private class TestNegotiateState : INegotiateState + { + private string _protocol; + private bool Stage1Complete { get; set; } + public bool IsCompleted { get; private set; } + public bool IsDisposed { get; private set; } + + public string Protocol + { + get + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (!Stage1Complete) + { + throw new InvalidOperationException("Authentication has not started yet."); + } + return _protocol; + } + } + + public void Dispose() + { + IsDisposed = true; + } + + public IIdentity GetIdentity() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (!IsCompleted) + { + throw new InvalidOperationException("Authentication is not complete."); + } + return new GenericIdentity("name", _protocol); + } + + public string GetOutgoingBlob(string incomingBlob) + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (IsCompleted) + { + throw new InvalidOperationException("Authentication is already complete."); + } + switch (incomingBlob) + { + case "ClientNtlmBlob1": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + Stage1Complete = true; + _protocol = "NTLM"; + return "ServerNtlmBlob1"; + case "ClientNtlmBlob2": + Assert.True(Stage1Complete, nameof(Stage1Complete)); + Assert.Equal("NTLM", _protocol); + IsCompleted = true; + return "ServerNtlmBlob2"; + // Kerberos can require one or two stages + case "ClientKerberosBlob": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + _protocol = "Kerberos"; + Stage1Complete = true; + IsCompleted = true; + return "ServerKerberosBlob"; + case "ClientKerberosBlob1": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + _protocol = "Kerberos"; + Stage1Complete = true; + return "ServerKerberosBlob1"; + case "ClientKerberosBlob2": + Assert.True(Stage1Complete, nameof(Stage1Complete)); + Assert.Equal("Kerberos", _protocol); + IsCompleted = true; + return "ServerKerberosBlob2"; + default: + throw new InvalidOperationException(incomingBlob); + } + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj b/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj new file mode 100644 index 0000000000..f0f681cda4 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/Microsoft.AspNetCore.Authentication.Negotiate.Test.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.0 + + + + + + + + + + + + + + + diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs new file mode 100644 index 0000000000..bced9c7608 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/Negotiate.Test/NegotiateHandlerTests.cs @@ -0,0 +1,506 @@ +// 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.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Net.Http.Headers; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Authentication.Negotiate +{ + public class NegotiateHandlerTests + { + [Fact] + public async Task Anonymous_MissingConnectionFeatures_ThrowsNotSupported() + { + var server = await CreateServerAsync(); + + var ex = await Assert.ThrowsAsync(() => SendAsync(server, "/Anonymous1", connection: null)); + Assert.Equal("Negotiate authentication requires a server that supports IConnectionItemsFeature like Kestrel.", ex.Message); + } + + [Fact] + public async Task Anonymous_NoChallenge_NoOps() + { + var server = await CreateServerAsync(); + + var result = await SendAsync(server, "/Anonymous1", new TestConnection()); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + } + + [Fact] + public async Task Anonymous_Http2_NoOps() + { + var server = await CreateServerAsync(); + + var result = await SendAsync(server, "/Anonymous2", connection: null, http2: true); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + } + + [Fact] + public async Task Anonymous_Challenge_401Negotiate() + { + var server = await CreateServerAsync(); + + var result = await SendAsync(server, "/Authenticate", new TestConnection()); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Fact] + public async Task Anonymous_ChallengeHttp2_401Negotiate() + { + var server = await CreateServerAsync(); + + var result = await SendAsync(server, "/Authenticate", connection: null, http2: true); + // Clients will downgrade to HTTP/1.1 and authenticate. + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Fact] + public async Task NtlmStage1Auth_401NegotiateServerBlob1() + { + var server = await CreateServerAsync(); + var result = await SendAsync(server, "/404", new TestConnection(), "Negotiate ClientNtlmBlob1"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate ServerNtlmBlob1", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Fact] + public async Task AnonymousAfterNtlmStage1_Throws() + { + var server = await CreateServerAsync(); + var testConnection = new TestConnection(); + await NtlmStage1Auth(server, testConnection); + + var ex = await Assert.ThrowsAsync(() => SendAsync(server, "/404", testConnection)); + Assert.Equal("An anonymous request was received in between authentication handshake requests.", ex.Message); + } + + [Fact] + public async Task NtlmStage2Auth_WithoutStage1_Throws() + { + var server = await CreateServerAsync(); + + var ex = await Assert.ThrowsAsync(() => SendAsync(server, "/404", new TestConnection(), "Negotiate ClientNtlmBlob2")); + Assert.Equal("Stage1Complete", ex.UserMessage); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task NtlmStage1And2Auth_Success(bool persistNtlm) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = persistNtlm); + var testConnection = new TestConnection(); + await NtlmStage1And2Auth(server, testConnection); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task KerberosAuth_Success(bool persistKerberos) + { + var server = await CreateServerAsync(options => options.PersistKerberosCredentials = persistKerberos); + var testConnection = new TestConnection(); + await KerberosAuth(server, testConnection); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task KerberosTwoStageAuth_Success(bool persistKerberos) + { + var server = await CreateServerAsync(options => options.PersistKerberosCredentials = persistKerberos); + var testConnection = new TestConnection(); + await KerberosStage1And2Auth(server, testConnection); + } + + [Theory] + [InlineData("NTLM")] + [InlineData("Kerberos")] + [InlineData("Kerberos2")] + public async Task AnonymousAfterCompletedPersist_Cached(string protocol) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = options.PersistKerberosCredentials = true); + var testConnection = new TestConnection(); + if (protocol == "NTLM") + { + await NtlmStage1And2Auth(server, testConnection); + } + else if (protocol == "Kerberos2") + { + await KerberosStage1And2Auth(server, testConnection); + } + else + { + await KerberosAuth(server, testConnection); + } + + var result = await SendAsync(server, "/Authenticate", testConnection); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.False(result.Response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)); + } + + [Theory] + [InlineData("NTLM")] + [InlineData("Kerberos")] + [InlineData("Kerberos2")] + public async Task AnonymousAfterCompletedNoPersist_Denied(string protocol) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = options.PersistKerberosCredentials = false); + var testConnection = new TestConnection(); + if (protocol == "NTLM") + { + await NtlmStage1And2Auth(server, testConnection); + } + else if (protocol == "Kerberos2") + { + await KerberosStage1And2Auth(server, testConnection); + } + else + { + await KerberosAuth(server, testConnection); + } + + var result = await SendAsync(server, "/Authenticate", testConnection); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AuthHeaderAfterNtlmCompleted_ReAuthenticates(bool persist) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = persist); + var testConnection = new TestConnection(); + await NtlmStage1And2Auth(server, testConnection); + await NtlmStage1And2Auth(server, testConnection); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AuthHeaderAfterKerberosCompleted_ReAuthenticates(bool persist) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = persist); + var testConnection = new TestConnection(); + await KerberosAuth(server, testConnection); + await KerberosAuth(server, testConnection); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AuthHeaderAfterKerberos2StageCompleted_ReAuthenticates(bool persist) + { + var server = await CreateServerAsync(options => options.PersistNtlmCredentials = persist); + var testConnection = new TestConnection(); + await KerberosStage1And2Auth(server, testConnection); + await KerberosStage1And2Auth(server, testConnection); + } + + [Fact] + public async Task ApplicationExceptionReExecute_AfterComplete_DoesntReRun() + { + var builder = new HostBuilder() + .ConfigureServices(services => services + .AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(options => + { + options.StateFactory = new TestNegotiateStateFactory(); + })) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseTestServer(); + webHostBuilder.Configure(app => + { + app.UseExceptionHandler("/error"); + app.UseAuthentication(); + app.Run(context => + { + Assert.True(context.User.Identity.IsAuthenticated); + if (context.Request.Path.Equals("/error")) + { + return context.Response.WriteAsync("Error Handler"); + } + + throw new TimeZoneNotFoundException(); + }); + }); + }); + + var server = (await builder.StartAsync()).GetTestServer(); + + var testConnection = new TestConnection(); + await NtlmStage1Auth(server, testConnection); + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientNtlmBlob2"); + Assert.Equal(StatusCodes.Status500InternalServerError, result.Response.StatusCode); + Assert.False(result.Response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)); + } + + // Single Stage + private static async Task KerberosAuth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientKerberosBlob"); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.Equal("Negotiate ServerKerberosBlob", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task KerberosStage1And2Auth(TestServer server, TestConnection testConnection) + { + await KerberosStage1Auth(server, testConnection); + await KerberosStage2Auth(server, testConnection); + } + + private static async Task KerberosStage1Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientKerberosBlob1"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate ServerKerberosBlob1", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task KerberosStage2Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientKerberosBlob2"); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.Equal("Negotiate ServerKerberosBlob2", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task NtlmStage1And2Auth(TestServer server, TestConnection testConnection) + { + await NtlmStage1Auth(server, testConnection); + await NtlmStage2Auth(server, testConnection); + } + + private static async Task NtlmStage1Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/404", testConnection, "Negotiate ClientNtlmBlob1"); + Assert.Equal(StatusCodes.Status401Unauthorized, result.Response.StatusCode); + Assert.Equal("Negotiate ServerNtlmBlob1", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task NtlmStage2Auth(TestServer server, TestConnection testConnection) + { + var result = await SendAsync(server, "/Authenticate", testConnection, "Negotiate ClientNtlmBlob2"); + Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); + Assert.Equal("Negotiate ServerNtlmBlob2", result.Response.Headers[HeaderNames.WWWAuthenticate]); + } + + private static async Task CreateServerAsync(Action configureOptions = null) + { + var builder = new HostBuilder() + .ConfigureServices(services => services + .AddRouting() + .AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(options => + { + options.StateFactory = new TestNegotiateStateFactory(); + configureOptions?.Invoke(options); + })) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseTestServer(); + webHostBuilder.Configure(app => + { + app.UseRouting(); + app.UseAuthentication(); + app.UseEndpoints(ConfigureEndpoints); + }); + }); + + var server = (await builder.StartAsync()).GetTestServer(); + return server; + } + + private static void ConfigureEndpoints(IEndpointRouteBuilder builder) + { + builder.Map("/Anonymous1", context => + { + Assert.Equal("HTTP/1.1", context.Request.Protocol); + Assert.False(context.User.Identity.IsAuthenticated, "Anonymous"); + return Task.CompletedTask; + }); + + builder.Map("/Anonymous2", context => + { + Assert.Equal("HTTP/2", context.Request.Protocol); + Assert.False(context.User.Identity.IsAuthenticated, "Anonymous"); + return Task.CompletedTask; + }); + + builder.Map("/Authenticate", async context => + { + if (!context.User.Identity.IsAuthenticated) + { + await context.ChallengeAsync(); + return; + } + + Assert.Equal("HTTP/1.1", context.Request.Protocol); // Not HTTP/2 + var name = context.User.Identity.Name; + Assert.False(string.IsNullOrEmpty(name), "name"); + await context.Response.WriteAsync(name); + }); + + builder.Map("/AlreadyAuthenticated", async context => + { + Assert.Equal("HTTP/1.1", context.Request.Protocol); // Not HTTP/2 + Assert.True(context.User.Identity.IsAuthenticated, "Authenticated"); + var name = context.User.Identity.Name; + Assert.False(string.IsNullOrEmpty(name), "name"); + await context.Response.WriteAsync(name); + }); + + builder.Map("/Unauthorized", async context => + { + // Simulate Authorization failure + var result = await context.AuthenticateAsync(); + await context.ChallengeAsync(); + }); + + builder.Map("/SignIn", context => + { + return Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); + }); + + builder.Map("/signOut", context => + { + return Assert.ThrowsAsync(() => context.SignOutAsync()); + }); + } + + private static Task SendAsync(TestServer server, string path, TestConnection connection, string authorizationHeader = null, bool http2 = false) + { + return server.SendAsync(context => + { + context.Request.Protocol = http2 ? "HTTP/2" : "HTTP/1.1"; + context.Request.Path = path; + if (!string.IsNullOrEmpty(authorizationHeader)) + { + context.Request.Headers[HeaderNames.Authorization] = authorizationHeader; + } + if (connection != null) + { + context.Features.Set(connection); + context.Features.Set(connection); + } + }); + } + + private class TestConnection : IConnectionItemsFeature, IConnectionCompleteFeature + { + public IDictionary Items { get; set; } = new ConnectionItems(); + + public void OnCompleted(Func callback, object state) + { + } + } + + private class TestNegotiateStateFactory : INegotiateStateFactory + { + public INegotiateState CreateInstance() => new TestNegotiateState(); + } + + private class TestNegotiateState : INegotiateState + { + private string _protocol; + private bool Stage1Complete { get; set; } + public bool IsCompleted { get; private set; } + public bool IsDisposed { get; private set; } + + public string Protocol + { + get + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (!Stage1Complete) + { + throw new InvalidOperationException("Authentication has not started yet."); + } + return _protocol; + } + } + + public void Dispose() + { + IsDisposed = true; + } + + public IIdentity GetIdentity() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (!IsCompleted) + { + throw new InvalidOperationException("Authentication is not complete."); + } + return new GenericIdentity("name", _protocol); + } + + public string GetOutgoingBlob(string incomingBlob) + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(TestNegotiateState)); + } + if (IsCompleted) + { + throw new InvalidOperationException("Authentication is already complete."); + } + switch (incomingBlob) + { + case "ClientNtlmBlob1": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + Stage1Complete = true; + _protocol = "NTLM"; + return "ServerNtlmBlob1"; + case "ClientNtlmBlob2": + Assert.True(Stage1Complete, nameof(Stage1Complete)); + Assert.Equal("NTLM", _protocol); + IsCompleted = true; + return "ServerNtlmBlob2"; + // Kerberos can require one or two stages + case "ClientKerberosBlob": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + _protocol = "Kerberos"; + Stage1Complete = true; + IsCompleted = true; + return "ServerKerberosBlob"; + case "ClientKerberosBlob1": + Assert.False(Stage1Complete, nameof(Stage1Complete)); + _protocol = "Kerberos"; + Stage1Complete = true; + return "ServerKerberosBlob1"; + case "ClientKerberosBlob2": + Assert.True(Stage1Complete, nameof(Stage1Complete)); + Assert.Equal("Kerberos", _protocol); + IsCompleted = true; + return "ServerKerberosBlob2"; + default: + throw new InvalidOperationException(incomingBlob); + } + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Controllers/AuthTestController.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Controllers/AuthTestController.cs new file mode 100644 index 0000000000..ec201b996b --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Controllers/AuthTestController.cs @@ -0,0 +1,361 @@ +// 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.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Negotiate.Client.Controllers +{ + [Route("authtest")] + [ApiController] + public class AuthTestController : ControllerBase + { + private const int StatusCode600WrongStatusCode = 600; + private const int StatusCode601WrongUser = 601; + private const int StatusCode602WrongAuthType = 602; + private const int StatusCode603WrongAuthHeader = 603; + private const int StatusCode604WrongProtocol = 604; + + private const string Http11Protocol = "HTTP/1.1"; + private const string Http2Protocol = "HTTP/2"; + + [HttpGet] + [Route("Anonymous/Unrestricted")] + public async Task AnonymousUnrestricted([FromQuery] string server, [FromQuery] string protocol) + { + var client = CreateSocketHttpClient(server); + client.DefaultRequestVersion = GetProtocolVersion(protocol); + + var result = await client.GetAsync("auth/Unrestricted"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + || HasWrongProtocol(protocol, result.Version, out actionResult) + || HasUser(body, out actionResult)) + { + return actionResult; + } + + return Ok(); + } + + [HttpGet] + [Route("Anonymous/Authorized")] + public async Task AnonymousAuthorized([FromQuery] string server, [FromQuery] string protocol) + { + // Note WinHttpHandler cannot disable default credentials on localhost. + var client = CreateSocketHttpClient(server); + client.DefaultRequestVersion = GetProtocolVersion(protocol); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status401Unauthorized, result.StatusCode, body, out var actionResult) + || HasWrongProtocol(protocol, result.Version, out actionResult)) + { + return actionResult; + } + + var authHeader = result.Headers.WwwAuthenticate.ToString(); + + if (!string.Equals("Negotiate", authHeader)) + { + return StatusCode(StatusCode603WrongAuthHeader, authHeader); + } + + return Ok(); + } + + [HttpGet] + [Route("DefaultCredentials/Authorized")] + public async Task DefaultCredentialsAuthorized([FromQuery] string server, [FromQuery] string protocol) + { + // Note WinHttpHandler cannot disable default credentials on localhost. + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + // Automatic downgrade to HTTP/1.1 + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + return Ok(); + } + + [HttpGet] + [Route("AfterAuth/Unrestricted/Persist")] + public async Task AfterAuthUnrestrictedPersist([FromQuery] string server, [FromQuery] string protocol1, [FromQuery] string protocol2) + { + // Note WinHttpHandler cannot disable default credentials on localhost. + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol1); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + // Automatic downgrade to HTTP/1.1 + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "auth/Unrestricted") { Version = GetProtocolVersion(protocol2) }); + body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out actionResult) + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + return Ok(); + } + + [HttpGet] + [Route("AfterAuth/Unrestricted/NonPersist")] + public async Task AfterAuthUnrestrictedNonPersist([FromQuery] string server, [FromQuery] string protocol1, [FromQuery] string protocol2) + { + // Note WinHttpHandler cannot disable default credentials on localhost. + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol1); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + // Automatic downgrade to HTTP/1.1 + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "auth/Unrestricted") { Version = GetProtocolVersion(protocol2) }); + body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out actionResult) + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || HasUser(body, out actionResult)) + { + return actionResult; + } + + return Ok(); + } + + [HttpGet] + [Route("AfterAuth/Authorized/NonPersist")] + public async Task AfterAuthAuthorizedNonPersist([FromQuery] string server, [FromQuery] string protocol1, [FromQuery] string protocol2) + { + // Note WinHttpHandler cannot disable default credentials on localhost. + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol1); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + // Automatic downgrade to HTTP/1.1 + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "auth/Authorized") { Version = GetProtocolVersion(protocol2) }); + body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out actionResult) + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + return Ok(); + } + + [HttpGet] + [Route("Unauthorized")] + public async Task Unauthorized([FromQuery] string server, [FromQuery] string protocol) + { + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol); + + var result = await client.GetAsync("auth/Unauthorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status401Unauthorized, result.StatusCode, body, out var actionResult) + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult)) // HTTP/2 downgrades. + { + return actionResult; + } + + var authHeader = result.Headers.WwwAuthenticate.ToString(); + + if (!string.Equals("Negotiate", authHeader)) + { + return StatusCode(StatusCode603WrongAuthHeader, authHeader); + } + + return Ok(); + } + + [HttpGet] + [Route("AfterAuth/Unauthorized")] + public async Task AfterAuthUnauthorized([FromQuery] string server, [FromQuery] string protocol) + { + var client = CreateWinHttpClient(server, useDefaultCredentials: true); + client.DefaultRequestVersion = GetProtocolVersion(protocol); + + var result = await client.GetAsync("auth/Authorized"); + var body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status200OK, result.StatusCode, body, out var actionResult) + // Automatic downgrade to HTTP/1.1 + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult) + || MissingUser(body, out actionResult)) + { + return actionResult; + } + + result = await client.GetAsync("auth/Unauthorized"); + body = await result.Content.ReadAsStringAsync(); + + if (HasWrongStatusCode(StatusCodes.Status401Unauthorized, result.StatusCode, body, out actionResult) + || HasWrongProtocol(Http11Protocol, result.Version, out actionResult)) // HTTP/2 downgrades. + { + return actionResult; + } + + var authHeader = result.Headers.WwwAuthenticate.ToString(); + + if (!string.Equals("Negotiate", authHeader)) + { + return StatusCode(StatusCode603WrongAuthHeader, authHeader); + } + + return Ok(); + } + + private bool HasWrongStatusCode(int expected, HttpStatusCode actual, string body, out IActionResult actionResult) + { + if (expected != (int)actual) + { + actionResult = StatusCode(StatusCode600WrongStatusCode, $"{actual} {body}"); + return true; + } + actionResult = null; + return false; + } + + private bool HasWrongProtocol(string expected, Version actual, out IActionResult actionResult) + { + if ((expected == Http11Protocol && actual != new Version(1, 1)) + || (expected == Http2Protocol && actual != new Version(2, 0))) + { + actionResult = StatusCode(StatusCode604WrongProtocol, actual.ToString()); + return true; + } + actionResult = null; + return false; + } + + private bool MissingUser(string body, out IActionResult actionResult) + { + var details = JsonDocument.Parse(body).RootElement; + + if (string.IsNullOrEmpty(details.GetProperty("name").GetString())) + { + actionResult = StatusCode(StatusCode601WrongUser, body); + return true; + } + + if (string.IsNullOrEmpty(details.GetProperty("authenticationType").GetString())) + { + actionResult = StatusCode(StatusCode602WrongAuthType, body); + return true; + } + + actionResult = null; + return false; + } + + private bool HasUser(string body, out IActionResult actionResult) + { + var details = JsonDocument.Parse(body).RootElement; + + if (!string.IsNullOrEmpty(details.GetProperty("name").GetString())) + { + actionResult = StatusCode(StatusCode601WrongUser, body); + return true; + } + + if (!string.IsNullOrEmpty(details.GetProperty("authenticationType").GetString())) + { + actionResult = StatusCode(StatusCode602WrongAuthType, body); + return true; + } + + actionResult = null; + return false; + } + + // Normally you'd want to re-use clients, but we want to ensure we have fresh state for each test. + + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + private HttpClient CreateSocketHttpClient(string remote, bool useDefaultCredentials = false) + { + return new HttpClient(new HttpClientHandler() + { + UseDefaultCredentials = useDefaultCredentials, + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }) + { + BaseAddress = new Uri(remote), + }; + } + + // https://github.com/dotnet/corefx/issues/35195 SocketHttpHandler won't downgrade HTTP/2. WinHttpHandler does. + private HttpClient CreateWinHttpClient(string remote, bool useDefaultCredentials = false) + { + // WinHttpHandler always uses default credentials on localhost + return new HttpClient(new WinHttpHandler() + { + ServerCredentials = CredentialCache.DefaultCredentials, + ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }) + { + BaseAddress = new Uri(remote) + }; + } + + private Version GetProtocolVersion(string protocol) + { + switch (protocol) + { + case "HTTP/1.1": return new Version(1, 1); + case "HTTP/2": return new Version(2, 0); + default: throw new NotImplementedException(Request.Protocol); + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Negotiate.Client.csproj b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Negotiate.Client.csproj new file mode 100644 index 0000000000..55a22b834d --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Negotiate.Client.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.0 + OutOfProcess + + + + + + + + + + + + diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Program.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Program.cs new file mode 100644 index 0000000000..04676b121b --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Program.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Negotiate.Client +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Properties/launchSettings.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Properties/launchSettings.json new file mode 100644 index 0000000000..d4027d33b3 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:2517", + "sslPort": 44346 + } + }, + "profiles": {/* + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + },*/ + "Negotiate.Client": { + "commandName": "Project", + "launchBrowser": false, + "applicationUrl": "https://localhost:5005;http://localhost:5004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/ReadMe.md b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/ReadMe.md new file mode 100644 index 0000000000..a382593efb --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/ReadMe.md @@ -0,0 +1,4 @@ +Negotiate Client + +This project is part of a suite of cross machine tests. It's intended to be deployed to a client machine, controled via WebApi requests, +and make outbound authentication requests to a specified server. diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Startup.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Startup.cs new file mode 100644 index 0000000000..67bb147440 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/Startup.cs @@ -0,0 +1,39 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Negotiate.Client +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.Development.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.json new file mode 100644 index 0000000000..d9d9a9bff6 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Client/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Controllers/AuthController.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Controllers/AuthController.cs new file mode 100644 index 0000000000..3e699e7fec --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Controllers/AuthController.cs @@ -0,0 +1,46 @@ +// 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 Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Negotiate.Server.Controllers +{ + [Route("auth")] + [ApiController] + public class AuthController : ControllerBase + { + [HttpGet] + [Route("Unrestricted")] + public ObjectResult GetUnrestricted() + { + var user = HttpContext.User.Identity; + return new ObjectResult(new + { + user.Name, + user.AuthenticationType, + }); + } + + [HttpGet] + [Authorize] + [Route("Authorized")] + public ObjectResult GetAuthorized() + { + var user = HttpContext.User.Identity; + return new ObjectResult(new + { + user.Name, + user.AuthenticationType, + }); + } + + [HttpGet] + [Authorize] + [Route("Unauthorized")] + public ChallengeResult GetUnauthorized() + { + return Challenge(); + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Negotiate.Server.csproj b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Negotiate.Server.csproj new file mode 100644 index 0000000000..627f54bc35 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Negotiate.Server.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.0 + OutOfProcess + + + + + + + + + + + + diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Program.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Program.cs new file mode 100644 index 0000000000..f993cfe93e --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Program.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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Negotiate.Server +{ + public class Program + { + public static async Task Main(string[] args) + { + using var host1 = CreateHostBuilder(args.Append("Persist=true").ToArray()).Build(); + using var host2 = CreateHostBuilder(args.Append("Persist=false").ToArray()).Build(); + await host1.StartAsync(); + await host2.StartAsync(); + await host1.WaitForShutdownAsync(); // CTL+C + await host2.StopAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.ConfigureKestrel((context, options) => + { + if (string.Equals("true", context.Configuration["Persist"])) + { + options.ListenAnyIP(5000); + options.ListenAnyIP(5001, listenOptions => listenOptions.UseHttps()); + } + else + { + options.ListenAnyIP(5002); + options.ListenAnyIP(5003, listenOptions => listenOptions.UseHttps()); + } + }); + }); + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Properties/launchSettings.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Properties/launchSettings.json new file mode 100644 index 0000000000..089c887c88 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3049", + "sslPort": 44306 + } + }, + "profiles": { /* + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + },*/ + "Negotiate.Server": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "auth/user", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/ReadMe.md b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/ReadMe.md new file mode 100644 index 0000000000..9913fdf7c3 --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/ReadMe.md @@ -0,0 +1,5 @@ +Negotiate Server + +This project is part of a suite of cross machine tests. It's intended to be deployed to a server machine and invoked indirectly via a client. + +This project launches two servers on different ports, one with connection credential persistence enabled, and the other with it disabled. diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Startup.cs b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Startup.cs new file mode 100644 index 0000000000..9f2af6b18c --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/Startup.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 Microsoft.AspNetCore.Authentication.Negotiate; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Negotiate.Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(options => + { + var persist = string.Equals("true", Configuration["Persist"]); + options.PersistKerberosCredentials = persist; + options.PersistNtlmCredentials = persist; + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseDeveloperExceptionPage(); + + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.Development.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.json b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.json new file mode 100644 index 0000000000..4e1d4aa1bb --- /dev/null +++ b/src/Security/Authentication/Negotiate/test/testassets/Negotiate.Server/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.AspNetCore.Authentication": "Debug", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Security/Authentication/test/selfSigned.cer b/src/Security/Authentication/test/selfSigned.cer deleted file mode 100644 index 6acc7af5a606916b16c00aef0177b1e4858d7290..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 762 zcmXqLV)|y##Q10dGZP~dlR(?zA3ry9D6e$iICaDG9g9C1aI&##^D#5YvN9Nm8VVZ- zvN4CUFbi{eCzd4UC5EIHml(*2^BNi(7y_ZW0T4uq^BN&@acOH}R5IXYh&j54_7gG}>Bg5p{Ygf8TXGs{Hj>&cCUVOqs?tZJ;an`>l62y1kxZk6~ zuFIUiZ%3bNU>xhe=(iubQr+ix=sr_x=xJE8NI2KLQqo;fd(Gz=2b$I#RQrBm>AH-i zkKf$$tG(O&CFSU}8}lEB&lieWTW|EsKf7PUZrb(3?pt$Kb!0z}5Q&rz6-jM78g4V| zOK9EiW0L}SKCE8V|6ZL*&v??tg-6}5K9j#b@qni1DwnHlCKc7!6%DNGq>`U}x_Bt* z&8)Xu?r^5BnZzWM9eH~B>Cf(NE)%mvtAm{1mu~zL*wWUgc+@eEXB)@~?%T%FSzNdgJwziJ6gsaj}N};q$PvEvyx^x#(Bl{N+D2**46g^?wC$QZC9<=OZR2BCf#b{zy(PLnRJ+Rx zZ+pk)db%T3^V#*QS2}P! z;rBeTSMJYZ-nthS6M6nGRag%UASt!}7{?02=i_8vp TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys) + => TestMatrix.ForServers(ServerType.IISExpress, ServerType.HttpSys, ServerType.Kestrel) .WithTfms(Tfm.NetCoreApp30) .WithAllHostingModels(); [ConditionalTheory] [MemberData(nameof(TestVariants))] + // In theory it could work on these platforms but the client would need non-default credentials. + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] public async Task NtlmAuthentication(TestVariant variant) { var testName = $"NtlmAuthentication_{variant.Server}_{variant.Tfm}_{variant.Architecture}_{variant.ApplicationType}"; @@ -65,7 +67,14 @@ namespace ServerComparison.FunctionalTests response = await httpClient.GetAsync("/Restricted"); responseText = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + if (variant.Server == ServerType.Kestrel) + { + Assert.DoesNotContain("NTLM", response.Headers.WwwAuthenticate.ToString()); + } + else + { + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString()); + } Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString()); logger.LogInformation("Testing /Forbidden"); @@ -97,4 +106,4 @@ namespace ServerComparison.FunctionalTests } } } -} \ No newline at end of file +} diff --git a/src/Servers/testassets/ServerComparison.TestSites/ServerComparison.TestSites.csproj b/src/Servers/testassets/ServerComparison.TestSites/ServerComparison.TestSites.csproj index 73eec76d86..eb8d5f9d98 100644 --- a/src/Servers/testassets/ServerComparison.TestSites/ServerComparison.TestSites.csproj +++ b/src/Servers/testassets/ServerComparison.TestSites/ServerComparison.TestSites.csproj @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + diff --git a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs index c4e837db58..73810a6b80 100644 --- a/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs +++ b/src/Servers/testassets/ServerComparison.TestSites/StartupNtlmAuthentication.cs @@ -3,14 +3,39 @@ using System; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Negotiate; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ServerComparison.TestSites { public class StartupNtlmAuthentication { + public IConfiguration Configuration { get; } + public bool IsKestrel => string.Equals(Configuration["server"], "Microsoft.AspNetCore.Server.Kestrel"); + + public StartupNtlmAuthentication(IConfiguration configuration) + { + Configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + if (IsKestrel) + { + services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + } + else + { + services.AddAuthentication(IISDefaults.AuthenticationScheme); + } + } + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.Use(async (context, next) => @@ -31,6 +56,7 @@ namespace ServerComparison.TestSites } }); + app.UseAuthentication(); app.Use((context, next) => { if (context.Request.Path.Equals("/Anonymous")) @@ -46,17 +72,17 @@ namespace ServerComparison.TestSites } else { - return context.ChallengeAsync("Windows"); + return context.ChallengeAsync(); } } if (context.Request.Path.Equals("/Forbidden")) { - return context.ForbidAsync("Windows"); + return context.ForbidAsync(); } return context.Response.WriteAsync("Hello World"); }); } } -} \ No newline at end of file +} From ede92492234f91125e60cf0acd276b79d9a4f0a6 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Wed, 22 May 2019 09:36:52 -0700 Subject: [PATCH 33/41] React to Authorization refactor (#10453) --- .../samples/NegotiateAuthSample/NegotiateAuthSample.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj index df3d2e036b..225b146856 100644 --- a/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj +++ b/src/Security/Authentication/Negotiate/samples/NegotiateAuthSample/NegotiateAuthSample.csproj @@ -8,7 +8,7 @@ - + From 72e2d1321797e2e60f8dfa984a3dccf587434664 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 17 May 2019 15:12:47 -0700 Subject: [PATCH 34/41] Make JsonPatch netstandard2.0 Fixes https://github.com/aspnet/AspNetCore/issues/9812 --- .../JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.csproj | 6 +++--- ....cs => Microsoft.AspNetCore.JsonPatch.netstandard2.0.cs} | 0 .../JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/Features/JsonPatch/ref/{Microsoft.AspNetCore.JsonPatch.netcoreapp3.0.cs => Microsoft.AspNetCore.JsonPatch.netstandard2.0.cs} (100%) diff --git a/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.csproj index 1a9fa78e0f..c2eba6030a 100644 --- a/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.csproj +++ b/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netstandard2.0 - - + + diff --git a/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.netcoreapp3.0.cs b/src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.netstandard2.0.cs similarity index 100% rename from src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.netcoreapp3.0.cs rename to src/Features/JsonPatch/ref/Microsoft.AspNetCore.JsonPatch.netstandard2.0.cs diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj index 63fec7add9..5865069fdd 100644 --- a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj +++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj @@ -2,7 +2,7 @@ ASP.NET Core support for JSON PATCH. - netcoreapp3.0 + netstandard2.0 $(NoWarn);CS1591 true aspnetcore;json;jsonpatch From 52133ac43125262586a5bc39faa448db5e938dfd Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 22 May 2019 10:59:04 -0700 Subject: [PATCH 35/41] React to deprecation of Fedora.28.Amd64.Open (#10409) --- eng/helix/content/runtests.sh | 74 ++++++++++++++++++++++------------ eng/targets/Helix.Common.props | 2 +- eng/targets/Helix.targets | 10 ++++- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/eng/helix/content/runtests.sh b/eng/helix/content/runtests.sh index 0c79c692c6..b3a786ee05 100644 --- a/eng/helix/content/runtests.sh +++ b/eng/helix/content/runtests.sh @@ -1,9 +1,46 @@ #!/usr/bin/env bash + +test_binary_path="$1" +dotnet_sdk_version="$2" +dotnet_runtime_version="$3" +helix_queue_name="$4" + +RESET="\033[0m" +RED="\033[0;31m" +YELLOW="\033[0;33m" +MAGENTA="\033[0;95m" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Ensures every invocation of dotnet apps uses the same dotnet.exe +# Add $random to path to ensure tests don't expect dotnet to be in a particular path +export DOTNET_ROOT="$DIR/.dotnet$RANDOM" + +# Ensure dotnet comes first on PATH +export PATH="$DOTNET_ROOT:$PATH" + +# Prevent fallback to global .NET locations. This ensures our tests use the shared frameworks we specify and don't rollforward to something else that might be installed on the machine +export DOTNET_MULTILEVEL_LOOKUP=0 + +# Avoid contaminating userprofiles +# Add $random to path to ensure tests don't expect home to be in a particular path +export DOTNET_CLI_HOME="$DIR/.home$RANDOM" + +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + +# Used by SkipOnHelix attribute +export helix="$helix_queue_name" + + +RESET="\033[0m" +RED="\033[0;31m" +YELLOW="\033[0;33m" +MAGENTA="\033[0;95m" + curl -o dotnet-install.sh -sSL https://dot.net/v1/dotnet-install.sh if [ $? -ne 0 ]; then download_retries=3 while [ $download_retries -gt 0 ]; do - curl -sSL https://dot.net/v1/dotnet-install.sh + curl -o dotnet-install.sh -sSL https://dot.net/v1/dotnet-install.sh if [ $? -ne 0 ]; then let download_retries=download_retries-1 echo -e "${YELLOW}Failed to download dotnet-install.sh. Retries left: $download_retries.${RESET}" @@ -16,11 +53,11 @@ fi # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) chmod +x "dotnet-install.sh"; sync -./dotnet-install.sh --version $2 --install-dir $HELIX_CORRELATION_PAYLOAD/sdk +./dotnet-install.sh --version $dotnet_sdk_version --install-dir "$DOTNET_ROOT" if [ $? -ne 0 ]; then sdk_retries=3 while [ $sdk_retries -gt 0 ]; do - ./dotnet-install.sh --version $2 --install-dir $HELIX_CORRELATION_PAYLOAD/sdk + ./dotnet-install.sh --version $dotnet_sdk_version --install-dir "$DOTNET_ROOT" if [ $? -ne 0 ]; then let sdk_retries=sdk_retries-1 echo -e "${YELLOW}Failed to install .NET Core SDK $version. Retries left: $sdk_retries.${RESET}" @@ -30,11 +67,11 @@ if [ $? -ne 0 ]; then done fi -./dotnet-install.sh --runtime dotnet --version $3 --install-dir $HELIX_CORRELATION_PAYLOAD/sdk +./dotnet-install.sh --runtime dotnet --version $dotnet_runtime_version --install-dir "$DOTNET_ROOT" if [ $? -ne 0 ]; then runtime_retries=3 while [ $runtime_retries -gt 0 ]; do - ./dotnet-install.sh --runtime dotnet --version $3 --install-dir $HELIX_CORRELATION_PAYLOAD/sdk + ./dotnet-install.sh --runtime dotnet --version $dotnet_runtime_version --install-dir "$DOTNET_ROOT" if [ $? -ne 0 ]; then let runtime_retries=runtime_retries-1 echo -e "${YELLOW}Failed to install .NET Core runtime $version. Retries left: $runtime_retries.${RESET}" @@ -44,23 +81,7 @@ if [ $? -ne 0 ]; then done fi -export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - -# Ensures every invocation of dotnet apps uses the same dotnet.exe -export DOTNET_ROOT="$HELIX_CORRELATION_PAYLOAD/sdk" - -# Ensure dotnet comes first on PATH -export PATH="$DOTNET_ROOT:$PATH" - -# Prevent fallback to global .NET locations. This ensures our tests use the shared frameworks we specify and don't rollforward to something else that might be installed on the machine -export DOTNET_MULTILEVEL_LOOKUP=0 - -# Avoid contaminating userprofiles -export DOTNET_CLI_HOME="$HELIX_CORRELATION_PAYLOAD/home" - -export helix="$4" - -$DOTNET_ROOT/dotnet vstest $1 -lt >discovered.txt +$DOTNET_ROOT/dotnet vstest $test_binary_path -lt >discovered.txt if grep -q "Exception thrown" discovered.txt; then echo -e "${RED}Exception thrown during test discovery${RESET}". cat discovered.txt @@ -71,17 +92,18 @@ fi # We need to specify all possible Flaky filters that apply to this environment, because the flaky attribute # only puts the explicit filter traits the user provided in the flaky attribute # Filter syntax: https://github.com/Microsoft/vstest-docs/blob/master/docs/filter.md -NONFLAKY_FILTER="Flaky:All!=true&Flaky:Helix:All!=true&Flaky:Helix:Queue:All!=true&Flaky:Helix:Queue:$HELIX!=true" +NONFLAKY_FILTER="Flaky:All!=true&Flaky:Helix:All!=true&Flaky:Helix:Queue:All!=true&Flaky:Helix:Queue:$helix_queue_name!=true" echo "Running non-flaky tests." -$DOTNET_ROOT/dotnet vstest $1 --logger:trx --TestCaseFilter:"$NONFLAKY_FILTER" +$DOTNET_ROOT/dotnet vstest $test_binary_path --logger:trx --TestCaseFilter:"$NONFLAKY_FILTER" nonflaky_exitcode=$? if [ $nonflaky_exitcode != 0 ]; then echo "Non-flaky tests failed!" 1>&2 # DO NOT EXIT fi -FLAKY_FILTER="Flaky:All=true|Flaky:Helix:All=true|Flaky:Helix:Queue:All=true|Flaky:Helix:Queue:$HELIX=true" + +FLAKY_FILTER="Flaky:All=true|Flaky:Helix:All=true|Flaky:Helix:Queue:All=true|Flaky:Helix:Queue:$helix_queue_name=true" echo "Running known-flaky tests." -$DOTNET_ROOT/dotnet vstest $1 --TestCaseFilter:"$FLAKY_FILTER" +$DOTNET_ROOT/dotnet vstest $test_binary_path --TestCaseFilter:"$FLAKY_FILTER" if [ $? != 0 ]; then echo "Flaky tests failed!" 1>&2 # DO NOT EXIT diff --git a/eng/targets/Helix.Common.props b/eng/targets/Helix.Common.props index 76b2d615a9..f4cd02ece0 100644 --- a/eng/targets/Helix.Common.props +++ b/eng/targets/Helix.Common.props @@ -23,7 +23,7 @@ - + diff --git a/eng/targets/Helix.targets b/eng/targets/Helix.targets index 2ec7b469ea..901f9f75e4 100644 --- a/eng/targets/Helix.targets +++ b/eng/targets/Helix.targets @@ -60,6 +60,12 @@ Usage: dotnet build /t:Helix src/MyTestProject.csproj + + + <_HelixFriendlyNameTargetQueue>$(HelixTargetQueue) + <_HelixFriendlyNameTargetQueue Condition="$(HelixTargetQueue.Contains('@'))">$(HelixTargetQueue.Substring(1, $([MSBuild]::Subtract($(HelixTargetQueue.LastIndexOf(')')), 1)))) + + @@ -72,8 +78,8 @@ Usage: dotnet build /t:Helix src/MyTestProject.csproj $(TargetFileName) @(HelixPreCommand) @(HelixPostCommand) - call runtests.cmd $(TargetFileName) $(TargetFrameworkIdentifier) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppPackageVersion) $(HelixTargetQueue) - ./runtests.sh $(TargetFileName) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppPackageVersion) $(HelixTargetQueue) + call runtests.cmd $(TargetFileName) $(TargetFrameworkIdentifier) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppPackageVersion) $(_HelixFriendlyNameTargetQueue) + ./runtests.sh $(TargetFileName) $(NETCoreSdkVersion) $(MicrosoftNETCoreAppPackageVersion) $(_HelixFriendlyNameTargetQueue) $(HelixTimeout) From f26b87aa42ee646c1e65532fbbb53dba2104196e Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Wed, 22 May 2019 11:02:37 -0700 Subject: [PATCH 36/41] Check for Java in local SignalR build (#10455) --- src/SignalR/build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/build.cmd b/src/SignalR/build.cmd index b7a7ab4c70..a36772c967 100644 --- a/src/SignalR/build.cmd +++ b/src/SignalR/build.cmd @@ -1,3 +1,3 @@ @ECHO OFF SET RepoRoot=%~dp0..\..\ -%RepoRoot%build.cmd -projects %~dp0**\*.*proj %* +%RepoRoot%build.cmd -buildJava -projects %~dp0**\*.*proj %* From 2cc071a41d8ea4dcdb98f50115fed4af3d4bba94 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 22 May 2019 12:59:27 -0700 Subject: [PATCH 37/41] Update BuildFromSource.md to explain CS0006 errors (#10465) --- docs/BuildFromSource.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md index 82ed16067d..656bdf9d46 100644 --- a/docs/BuildFromSource.md +++ b/docs/BuildFromSource.md @@ -25,7 +25,7 @@ Building ASP.NET Core on Windows requires: * Oracle's JDK * To install a version of the JDK that will only be used by this repo, run [eng/scripts/InstallJdk.ps1](/eng/scripts/InstallJdk.ps1) ```ps1 - PS> ./eng/scripts/InstalLJdk.ps1 + PS> ./eng/scripts/InstallJdk.ps1 ``` ### macOS/Linux @@ -81,21 +81,24 @@ Instead, we have many .sln files which include a sub-set of projects. These prin > :bulb: Pro tip: `dotnet new sln` and `dotnet sln` are one of the easiest ways to create and modify solutions. -### Known issue: NU1105 +### Common error: CS0006 -Opening solution files may produce an error code NU1105 with a message such +Opening solution files and building may produce an error code CS0006 with a message such -> Unable to find project information for 'C:\src\AspNetCore\src\Hosting\Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj'. Inside Visual Studio, this may be because the project is unloaded or not part of current solution. Otherwise the project file may be invalid or missing targets required for restore. +> Error CS0006 Metadata file 'C:\src\aspnet\AspNetCore\artifacts\bin\Microsoft.AspNetCore.Metadata\Debug\netstandard2.0\Microsoft.AspNetCore.Metadata.dll' could not be found -This is a known issue in NuGet () and we are working with them for a solution. See also to track progress on this. +The cause of this problem is that the solution you are using does not include the project that produces this .dll. This most often occurs after we have added new projects to the repo, but failed to update our .sln files to include the new project. In some cases, it is sometimes the intended behavior of the .sln which has been crafted to only include a subset of projects. -**The workaround** for now is to add all projects to the solution. You can either do this one by one using `dotnet sln` - - dotnet sln add C:\src\AspNetCore\src\Hosting\Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj - -Or you can use this script to automatically traverse the project reference graph, which then invokes `dotnet sln` for you: [eng/scripts/AddAllProjectRefsToSolution.ps1](/eng/scripts/AddAllProjectRefsToSolution.ps1). - - ./eng/scripts/AddAllProjectRefsToSolution.ps1 -WorkingDir src/Mvc/ +**You can fix this in one of two ways** +1. Build the project on command line. In most cases, running `build.cmd` on command line solve this problem. +2. Update the solution to include the missing project. You can either do this one by one using `dotnet sln` + ``` + dotnet sln add C:\src\AspNetCore\src\Hosting\Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj + ``` + Or you can use this script to automatically traverse the project reference graph, which then invokes `dotnet sln` for you: [eng/scripts/AddAllProjectRefsToSolution.ps1](/eng/scripts/AddAllProjectRefsToSolution.ps1). + ``` + ./eng/scripts/AddAllProjectRefsToSolution.ps1 -WorkingDir src/Mvc/ + ``` ## Building with Visual Studio Code From dbe9ab7dd5b02d99ede1ca37c0bf848bdaedda60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20L=C5=91czi?= Date: Wed, 22 May 2019 23:37:08 +0200 Subject: [PATCH 38/41] Respect CancellationToken in HubConnection.StartAsync() (#10140) --- .../csharp/Client.Core/src/HubConnection.cs | 15 ++++++---- .../Client/test/UnitTests/TestTransport.cs | 5 ++-- ...e.Http.Connections.Client.netcoreapp3.0.cs | 8 +++--- ....Http.Connections.Client.netstandard2.0.cs | 8 +++--- .../src/HttpConnection.cs | 28 +++++++++---------- .../src/Internal/ITransport.cs | 3 +- .../src/Internal/LongPollingTransport.cs | 4 +-- .../src/Internal/ServerSentEventsTransport.cs | 4 +-- .../src/Internal/WebSocketsTransport.cs | 4 +-- .../server/SignalR/test/EndToEndTests.cs | 2 +- 10 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index 560d32978e..13ddb56792 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -253,7 +253,10 @@ namespace Microsoft.AspNetCore.SignalR.Client throw new InvalidOperationException($"The {nameof(HubConnection)} cannot be started while {nameof(StopAsync)} is running."); } - await StartAsyncCore(cancellationToken); + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _state.StopCts.Token)) + { + await StartAsyncCore(cancellationToken); + } _state.ChangeState(HubConnectionState.Connecting, HubConnectionState.Connected); } @@ -422,7 +425,7 @@ namespace Microsoft.AspNetCore.SignalR.Client Log.Starting(_logger); // Start the connection - var connection = await _connectionFactory.ConnectAsync(_protocol.TransferFormat); + var connection = await _connectionFactory.ConnectAsync(_protocol.TransferFormat, cancellationToken); var startingConnectionState = new ConnectionState(connection, this); // From here on, if an error occurs we need to shut down the connection because @@ -1023,7 +1026,8 @@ namespace Microsoft.AspNetCore.SignalR.Client try { using (var handshakeCts = new CancellationTokenSource(HandshakeTimeout)) - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, handshakeCts.Token, _state.StopCts.Token)) + // cancellationToken already contains _state.StopCts.Token, so we don't have to link it again + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, handshakeCts.Token)) { while (true) { @@ -1287,7 +1291,7 @@ namespace Microsoft.AspNetCore.SignalR.Client var reconnectStartTime = DateTime.UtcNow; var retryReason = closeException; var nextRetryDelay = GetNextRetryDelay(previousReconnectAttempts++, TimeSpan.Zero, retryReason); - + // We still have the connection lock from the caller, HandleConnectionClose. _state.AssertInConnectionLock(); @@ -1347,8 +1351,7 @@ namespace Microsoft.AspNetCore.SignalR.Client SafeAssert(ReferenceEquals(_state.CurrentConnectionStateUnsynchronized, null), "Someone other than Reconnect set the connection state!"); - // HandshakeAsync already checks ReconnectingConnectionState.StopCts.Token. - await StartAsyncCore(CancellationToken.None); + await StartAsyncCore(_state.StopCts.Token); Log.Reconnected(_logger, previousReconnectAttempts, DateTime.UtcNow - reconnectStartTime); diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/TestTransport.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/TestTransport.cs index e6395692c5..ae1249dba7 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/TestTransport.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/TestTransport.cs @@ -1,5 +1,6 @@ using System; using System.IO.Pipelines; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections.Client; @@ -29,13 +30,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests Format = transferFormat; } - public async Task StartAsync(Uri url, TransferFormat transferFormat) + public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) { if ((Format & transferFormat) == 0) { throw new InvalidOperationException($"The '{transferFormat}' transfer format is not supported by this transport."); } - + var options = ClientPipeOptions.DefaultOptions; var pair = DuplexPipe.CreateConnectionPair(options, options); diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs index df268f20ba..dd78ecdf90 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { public partial interface ITransport : System.IO.Pipelines.IDuplexPipe { - System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat); + System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.Task StopAsync(); } public partial interface ITransportFactory @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs index df268f20ba..dd78ecdf90 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/ref/Microsoft.AspNetCore.Http.Connections.Client.netstandard2.0.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { public partial interface ITransport : System.IO.Pipelines.IDuplexPipe { - System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat); + System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.Task StopAsync(); } public partial interface ITransportFactory @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public System.IO.Pipelines.PipeReader Input { get { throw null; } } public System.IO.Pipelines.PipeWriter Output { get { throw null; } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat) { throw null; } + public System.Threading.Tasks.Task StartAsync(System.Uri url, Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task StopAsync() { throw null; } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 232c8c5c3b..fdbecdef64 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -188,11 +188,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Client { using (_logger.BeginScope(_logScope)) { - await StartAsyncCore(transferFormat).ForceAsync(); + await StartAsyncCore(transferFormat, cancellationToken).ForceAsync(); } } - private async Task StartAsyncCore(TransferFormat transferFormat) + private async Task StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken) { CheckDisposed(); @@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client Log.Starting(_logger); - await SelectAndStartTransport(transferFormat); + await SelectAndStartTransport(transferFormat, cancellationToken); _started = true; Log.Started(_logger); @@ -288,7 +288,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client } } - private async Task SelectAndStartTransport(TransferFormat transferFormat) + private async Task SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken) { var uri = _httpConnectionOptions.Url; // Set the initial access token provider back to the original one from options @@ -301,7 +301,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client if (_httpConnectionOptions.Transports == HttpTransportType.WebSockets) { Log.StartingTransport(_logger, _httpConnectionOptions.Transports, uri); - await StartTransport(uri, _httpConnectionOptions.Transports, transferFormat); + await StartTransport(uri, _httpConnectionOptions.Transports, transferFormat, cancellationToken); } else { @@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client do { - negotiationResponse = await GetNegotiationResponseAsync(uri); + negotiationResponse = await GetNegotiationResponseAsync(uri, cancellationToken); if (negotiationResponse.Url != null) { @@ -379,12 +379,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client // The negotiation response gets cleared in the fallback scenario. if (negotiationResponse == null) { - negotiationResponse = await GetNegotiationResponseAsync(uri); + negotiationResponse = await GetNegotiationResponseAsync(uri, cancellationToken); connectUrl = CreateConnectUrl(uri, negotiationResponse.ConnectionId); } Log.StartingTransport(_logger, transportType, connectUrl); - await StartTransport(connectUrl, transportType, transferFormat); + await StartTransport(connectUrl, transportType, transferFormat, cancellationToken); break; } } @@ -414,7 +414,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client } } - private async Task NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger) + private async Task NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken) { try { @@ -436,7 +436,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client // rather than buffer the entire response. This gives a small perf boost. // Note that it is important to dispose of the response when doing this to // avoid leaving the connection open. - using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) + using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { response.EnsureSuccessStatusCode(); var responseBuffer = await response.Content.ReadAsByteArrayAsync(); @@ -467,7 +467,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client return Utils.AppendQueryString(url, "id=" + connectionId); } - private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat) + private async Task StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat, CancellationToken cancellationToken) { // Construct the transport var transport = _transportFactory.CreateTransport(transportType); @@ -475,7 +475,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client // Start the transport, giving it one end of the pipe try { - await transport.StartAsync(connectUrl, transferFormat); + await transport.StartAsync(connectUrl, transferFormat, cancellationToken); } catch (Exception ex) { @@ -604,9 +604,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Client #endif } - private async Task GetNegotiationResponseAsync(Uri uri) + private async Task GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken) { - var negotiationResponse = await NegotiateAsync(uri, _httpClient, _logger); + var negotiationResponse = await NegotiateAsync(uri, _httpClient, _logger, cancellationToken); _connectionId = negotiationResponse.ConnectionId; _logScope.ConnectionId = _connectionId; return negotiationResponse; diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ITransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ITransport.cs index 8f133a2d5d..fe9d94c8bf 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ITransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ITransport.cs @@ -3,6 +3,7 @@ using System; using System.IO.Pipelines; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { public interface ITransport : IDuplexPipe { - Task StartAsync(Uri url, TransferFormat transferFormat); + Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default); Task StopAsync(); } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/LongPollingTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/LongPollingTransport.cs index f2a92e71d3..9d953f0114 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/LongPollingTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/LongPollingTransport.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger(); } - public async Task StartAsync(Uri url, TransferFormat transferFormat) + public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) { if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text) { @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // Make initial long polling request // Server uses first long polling request to finish initializing connection and it returns without data var request = new HttpRequestMessage(HttpMethod.Get, url); - using (var response = await _httpClient.SendAsync(request)) + using (var response = await _httpClient.SendAsync(request, cancellationToken)) { response.EnsureSuccessStatusCode(); } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsTransport.cs index b0f3477a2b..d3bebe3160 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/ServerSentEventsTransport.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger(); } - public async Task StartAsync(Uri url, TransferFormat transferFormat) + public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) { if (transferFormat != TransferFormat.Text) { @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal try { - response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); + response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); } catch diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs index 4fe1e61b73..749c85d8d2 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal _accessTokenProvider = accessTokenProvider; } - public async Task StartAsync(Uri url, TransferFormat transferFormat) + public async Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) { if (url == null) { @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal try { - await _webSocket.ConnectAsync(resolvedUrl, CancellationToken.None); + await _webSocket.ConnectAsync(resolvedUrl, cancellationToken); } catch { diff --git a/src/SignalR/server/SignalR/test/EndToEndTests.cs b/src/SignalR/server/SignalR/test/EndToEndTests.cs index f1c951c57c..ae59219a94 100644 --- a/src/SignalR/server/SignalR/test/EndToEndTests.cs +++ b/src/SignalR/server/SignalR/test/EndToEndTests.cs @@ -568,7 +568,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } - public Task StartAsync(Uri url, TransferFormat transferFormat) + public Task StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken = default) { var options = ClientPipeOptions.DefaultOptions; var pair = DuplexPipe.CreateConnectionPair(options, options); From 6c5e1690ad6e4aa7a2fc1dfbbf193f8f6511fbc6 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 23 May 2019 13:10:53 +0100 Subject: [PATCH 39/41] Update Mono WebAssembly for Blazor (https://github.com/aspnet/Blazor/pull/1807) --- build/sources.props | 5 + eng/Versions.props | 2 +- .../src/targets/Blazor.MonoRuntime.props | 2 +- .../src/MonoDebugProxy/UpdateSources.cmd | 20 -- .../src/MonoDebugProxy/ws-proxy/DebugStore.cs | 259 +++++++++++++----- .../src/MonoDebugProxy/ws-proxy/MonoProxy.cs | 159 +++++++++-- .../src/MonoDebugProxy/ws-proxy/WsProxy.cs | 6 +- 7 files changed, 330 insertions(+), 123 deletions(-) delete mode 100644 src/Components/Blazor/Server/src/MonoDebugProxy/UpdateSources.cmd diff --git a/build/sources.props b/build/sources.props index bcfbefc996..26d8be6677 100644 --- a/build/sources.props +++ b/build/sources.props @@ -27,6 +27,11 @@ $(RestoreSources); https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; + + + $(RestoreSources); + https://dotnet.myget.org/F/blazor-dev/api/v3/index.json; + https://dotnetcli.blob.core.windows.net/dotnet/ diff --git a/eng/Versions.props b/eng/Versions.props index bc32721fd6..10a317d441 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -165,7 +165,7 @@ 1.4.0 5.3.0 - 0.10.0-preview-20190325.1 + 0.10.0-preview-20190523.1 2.1.1 2.2.0 diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props index 682dbeed28..03f70748ff 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props @@ -6,7 +6,7 @@ none - --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true + --disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true dist/ $(BaseBlazorDistPath)_content/ $(BaseBlazorDistPath)_framework/ diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/UpdateSources.cmd b/src/Components/Blazor/Server/src/MonoDebugProxy/UpdateSources.cmd deleted file mode 100644 index 591f2f9950..0000000000 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/UpdateSources.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -echo |---- -echo | Copying the ws-proxy sources here is a temporary step until ws-proxy is -echo | distributed as a NuGet package. -echo | ... -echo | Instead of dealing with Git submodules, this script simply fetches the -echo | latest sources so they can be built directly inside this project (hence -echo | we don't have to publish our own separate package for this). -echo | ... -echo | When updating, you'll need to re-apply any patches we've made manually. -echo |---- -@echo on - -cd /D "%~dp0" -rmdir /s /q ws-proxy -git clone https://github.com/kumpera/ws-proxy.git -rmdir /s /q ws-proxy\.git -del ws-proxy\*.csproj -del ws-proxy\*.sln -del ws-proxy\Program.cs diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs index 9d29cc494a..ee28bd94b7 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs @@ -6,6 +6,9 @@ using Mono.Cecil.Cil; using System.Linq; using Newtonsoft.Json.Linq; using System.Net.Http; +using Mono.Cecil.Pdb; +using Newtonsoft.Json; +using System.Text.RegularExpressions; namespace WsProxy { internal class BreakPointRequest { @@ -18,17 +21,29 @@ namespace WsProxy { return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; } - public static BreakPointRequest Parse (JObject args) + public static BreakPointRequest Parse (JObject args, DebugStore store) { if (args == null) return null; var url = args? ["url"]?.Value (); - if (!url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) + if (url == null) { + var urlRegex = args?["urlRegex"].Value(); + var sourceFile = store.GetFileByUrlRegex (urlRegex); + + url = sourceFile?.DotNetUrl; + } + + if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) { + var sourceFile = store.GetFileByUrl (url); + url = sourceFile?.DotNetUrl; + } + + if (url == null) return null; - var parts = url.Substring ("dotnet://".Length).Split ('/'); - if (parts.Length != 2) + var parts = ParseDocumentUrl (url); + if (parts.Assembly == null) return null; var line = args? ["lineNumber"]?.Value (); @@ -37,12 +52,24 @@ namespace WsProxy { return null; return new BreakPointRequest () { - Assembly = parts [0], - File = parts [1], + Assembly = parts.Assembly, + File = parts.DocumentPath, Line = line.Value, Column = column.Value }; } + + static (string Assembly, string DocumentPath) ParseDocumentUrl (string url) + { + if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") { + return ( + docUri.Host, + docUri.PathAndQuery.Substring (1) + ); + } else { + return (null, null); + } + } } @@ -101,7 +128,7 @@ namespace WsProxy { public SourceLocation (MethodInfo mi, SequencePoint sp) { this.id = mi.SourceId; - this.line = sp.StartLine; + this.line = sp.StartLine - 1; this.column = sp.StartColumn - 1; this.cliLoc = new CliLocation (mi, sp.Offset); } @@ -260,13 +287,13 @@ namespace WsProxy { } } - internal class AssemblyInfo { static int next_id; ModuleDefinition image; readonly int id; Dictionary methods = new Dictionary (); - readonly List sources = new List (); + Dictionary sourceLinkMappings = new Dictionary(); + readonly List sources = new List(); public AssemblyInfo (byte[] assembly, byte[] pdb) { @@ -274,16 +301,35 @@ namespace WsProxy { this.id = ++next_id; } - ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); - if (pdb != null) { - rp.ReadSymbols = true; - rp.SymbolReaderProvider = new PortablePdbReaderProvider (); - rp.SymbolStream = new MemoryStream (pdb); + try { + ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); + if (pdb != null) { + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new PortablePdbReaderProvider (); + rp.SymbolStream = new MemoryStream (pdb); + } + + rp.ReadingMode = ReadingMode.Immediate; + rp.InMemory = true; + + this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); + } catch (BadImageFormatException ex) { + Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}"); } - rp.InMemory = true; + if (this.image == null) { + ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); + if (pdb != null) { + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new NativePdbReaderProvider (); + rp.SymbolStream = new MemoryStream (pdb); + } - this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); + rp.ReadingMode = ReadingMode.Immediate; + rp.InMemory = true; + + this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); + } Populate (); } @@ -294,6 +340,8 @@ namespace WsProxy { void Populate () { + ProcessSourceLink(); + var d2s = new Dictionary (); Func get_src = (doc) => { @@ -301,33 +349,88 @@ namespace WsProxy { return null; if (d2s.ContainsKey (doc)) return d2s [doc]; - var src = new SourceFile (this, sources.Count, doc); + var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url)); sources.Add (src); d2s [doc] = src; return src; }; - foreach (var m in image.GetTypes ().SelectMany (t => t.Methods)) { + foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) { Document first_doc = null; foreach (var sp in m.DebugInformation.SequencePoints) { - if (first_doc == null) { + if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) { first_doc = sp.Document; - } else if (first_doc != sp.Document) { - //FIXME this is needed for (c)ctors in corlib - throw new Exception ($"Cant handle multi-doc methods in {m}"); } + // else if (first_doc != sp.Document) { + // //FIXME this is needed for (c)ctors in corlib + // throw new Exception ($"Cant handle multi-doc methods in {m}"); + //} } - var src = get_src (first_doc); - var mi = new MethodInfo (this, m, src); - int mt = (int)m.MetadataToken.RID; - this.methods [mt] = mi; - if (src != null) - src.AddMethod (mi); + if (first_doc == null) { + // all generated files + first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document; + } + if (first_doc != null) { + var src = get_src (first_doc); + var mi = new MethodInfo (this, m, src); + int mt = (int)m.MetadataToken.RID; + this.methods [mt] = mi; + if (src != null) + src.AddMethod (mi); + } } } + private void ProcessSourceLink () + { + var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink); + + if (sourceLinkDebugInfo != null) { + var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + + if (sourceLinkContent != null) { + var jObject = JObject.Parse (sourceLinkContent) ["documents"]; + sourceLinkMappings = JsonConvert.DeserializeObject> (jObject.ToString ()); + } + } + } + + private Uri GetSourceLinkUrl (string document) + { + if (sourceLinkMappings.TryGetValue (document, out string url)) { + return new Uri (url); + } + + foreach (var sourceLinkDocument in sourceLinkMappings) { + string key = sourceLinkDocument.Key; + + if (Path.GetFileName (key) != "*") { + continue; + } + + var keyTrim = key.TrimEnd ('*'); + + if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) { + var docUrlPart = document.Replace (keyTrim, ""); + return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart); + } + } + + return null; + } + + private string GetRelativePath (string relativeTo, string path) + { + var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute); + var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) { + rel = $".{ Path.DirectorySeparatorChar }{ rel }"; + } + return rel; + } + public IEnumerable Sources { get { return this.sources; } } @@ -342,9 +445,9 @@ namespace WsProxy { public MethodInfo GetMethodByToken (int token) { - return methods [token]; + methods.TryGetValue (token, out var value); + return value; } - } internal class SourceFile { @@ -353,23 +456,36 @@ namespace WsProxy { int id; Document doc; - internal SourceFile (AssemblyInfo assembly, int id, Document doc) + internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) { this.methods = new HashSet (); + this.SourceLinkUri = sourceLinkUri; this.assembly = assembly; this.id = id; this.doc = doc; + this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", ""); + + this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute); + if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) { + this.Url = this.SourceUri.ToString (); + } else { + this.Url = DotNetUrl; + } + } internal void AddMethod (MethodInfo mi) { this.methods.Add (mi); } - public string FileName => Path.GetFileName (doc.Url); - public string Url => $"dotnet://{assembly.Name}/{FileName}"; + public string DebuggerFileName { get; } + public string Url { get; } + public string AssemblyName => assembly.Name; + public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}"; public string DocHashCode => "abcdee" + id; public SourceId SourceId => new SourceId (assembly.Id, this.id); - public string LocalPath => doc.Url; + public Uri SourceLinkUri { get; } + public Uri SourceUri { get; } public IEnumerable Methods => this.methods; } @@ -377,17 +493,18 @@ namespace WsProxy { internal class DebugStore { List assemblies = new List (); - public DebugStore (string[] loaded_files) + public DebugStore (string [] loaded_files) { - bool MatchPdb (string asm, string pdb) { + bool MatchPdb (string asm, string pdb) + { return Path.ChangeExtension (asm, "pdb") == pdb; } var asm_files = new List (); var pdb_files = new List (); foreach (var f in loaded_files) { - var file_name = f.ToLower (); - if (file_name.EndsWith (".pdb", StringComparison.Ordinal)) + var file_name = f; + if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase)) pdb_files.Add (file_name); else asm_files.Add (file_name); @@ -395,14 +512,18 @@ namespace WsProxy { //FIXME make this parallel foreach (var p in asm_files) { - var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n)); - HttpClient h = new HttpClient (); - var assembly_bytes = h.GetByteArrayAsync (p).Result; - byte[] pdb_bytes = null; - if (pdb != null) - pdb_bytes = h.GetByteArrayAsync (pdb).Result; + try { + var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n)); + HttpClient h = new HttpClient (); + var assembly_bytes = h.GetByteArrayAsync (p).Result; + byte [] pdb_bytes = null; + if (pdb != null) + pdb_bytes = h.GetByteArrayAsync (pdb).Result; - this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes)); + this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes)); + } catch (Exception e) { + Console.WriteLine ($"Failed to read {p} ({e.Message})"); + } } } @@ -412,7 +533,6 @@ namespace WsProxy { foreach (var s in a.Sources) yield return s; } - } public SourceFile GetFileById (SourceId id) @@ -426,26 +546,23 @@ namespace WsProxy { } /* - Matching logic here is hilarious and it goes like this: - We inject one line at the top of all sources to make it easy to identify them [1]. V8 uses zero based indexing for both line and column. PPDBs uses one based indexing for both line and column. - Which means that: - - for lines, values are already adjusted (v8 numbers come +1 due to the injected line) - - for columns, we need to +1 the v8 numbers - [1] It's so we can deal with the Runtime.compileScript ide cmd */ static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end) { - if (start.Line > sp.StartLine) + var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); + var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); + + if (start.Line > spStart.Line) return false; - if ((start.Column + 1) > sp.StartColumn && start.Line == sp.StartLine) + if (start.Column > spStart.Column && start.Line == sp.StartLine) return false; - if (end.Line < sp.EndLine) + if (end.Line < spEnd.Line) return false; - if ((end.Column + 1) < sp.EndColumn && end.Line == sp.EndLine) + if (end.Column < spEnd.Column && end.Line == spEnd.Line) return false; return true; @@ -477,28 +594,24 @@ namespace WsProxy { } /* - Matching logic here is hilarious and it goes like this: - We inject one line at the top of all sources to make it easy to identify them [1]. V8 uses zero based indexing for both line and column. PPDBs uses one based indexing for both line and column. - Which means that: - - for lines, values are already adjusted (v8 numbers come + 1 due to the injected line) - - for columns, we need to +1 the v8 numbers - [1] It's so we can deal with the Runtime.compileScript ide cmd */ static bool Match (SequencePoint sp, int line, int column) { - if (sp.StartLine > line || sp.EndLine < line) + var bp = (line: line + 1, column: column + 1); + + if (sp.StartLine > bp.line || sp.EndLine < bp.line) return false; //Chrome sends a zero column even if getPossibleBreakpoints say something else if (column == 0) return true; - if (sp.StartColumn > (column + 1) && sp.StartLine == line) + if (sp.StartColumn > bp.column && sp.StartLine == bp.line) return false; - if (sp.EndColumn < (column + 1) && sp.EndLine == line) + if (sp.EndColumn < bp.column && sp.EndLine == bp.line) return false; return true; @@ -506,8 +619,11 @@ namespace WsProxy { public SourceLocation FindBestBreakpoint (BreakPointRequest req) { - var asm = this.assemblies.FirstOrDefault (a => a.Name == req.Assembly); - var src = asm.Sources.FirstOrDefault (s => s.FileName == req.File); + var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase)); + var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase)); + + if (src == null) + return null; foreach (var m in src.Methods) { foreach (var sp in m.methodDef.DebugInformation.SequencePoints) { @@ -521,8 +637,15 @@ namespace WsProxy { } public string ToUrl (SourceLocation location) + => location != null ? GetFileById (location.Id).Url : ""; + + public SourceFile GetFileByUrlRegex (string urlRegex) { - return GetFileById (location.Id).Url; + var regex = new Regex (urlRegex); + return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString())); } + + public SourceFile GetFileByUrl (string url) + => AllSources ().FirstOrDefault (file => file.Url.ToString() == url); } } diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs index ae6343bca4..6ff776bffe 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -8,6 +8,7 @@ using System.Threading; using System.IO; using System.Text; using System.Collections.Generic; +using System.Net; namespace WsProxy { @@ -20,6 +21,8 @@ namespace WsProxy { public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})"; public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()"; public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()"; + public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})"; + public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})"; } internal enum MonoErrorCodes { @@ -128,7 +131,7 @@ namespace WsProxy { case "Debugger.getScriptSource": { var script_id = args? ["scriptId"]?.Value (); if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) { - OnGetScriptSource (id, script_id, token); + await OnGetScriptSource (id, script_id, token); return true; } @@ -154,7 +157,7 @@ namespace WsProxy { case "Debugger.setBreakpointByUrl": { Info ($"BP req {args}"); - var bp_req = BreakPointRequest.Parse (args); + var bp_req = BreakPointRequest.Parse (args, store); if (bp_req != null) { await SetBreakPoint (id, bp_req, token); return true; @@ -200,7 +203,14 @@ namespace WsProxy { await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token); return true; } - + if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture)) + { + if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture)) + await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES); + if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture)) + await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES); + return true; + } break; } } @@ -213,6 +223,7 @@ namespace WsProxy { Info ("RUNTIME READY, PARTY TIME"); await RuntimeReady (token); await SendCommand ("Debugger.resume", new JObject (), token); + SendEvent ("Mono.runtimeReady", new JObject (), token); } async Task OnBreakPointHit (JObject args, CancellationToken token) @@ -257,9 +268,9 @@ namespace WsProxy { var src = bp == null ? null : store.GetFileById (bp.Location.Id); var callFrames = new List (); - foreach (var f in orig_callframes) { - var function_name = f ["functionName"]?.Value (); - var url = f ["url"]?.Value (); + foreach (var frame in orig_callframes) { + var function_name = frame ["functionName"]?.Value (); + var url = frame ["url"]?.Value (); if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { var frames = new List (); int frame_id = 0; @@ -271,14 +282,19 @@ namespace WsProxy { var asm = store.GetAssemblyByName (assembly_name); var method = asm.GetMethodByToken (method_token); - var location = method.GetLocationByIl (il_pos); + + if (method == null) { + Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); + continue; + } + + var location = method?.GetLocationByIl (il_pos); // When hitting a breakpoint on the "IncrementCount" method in the standard // Blazor project template, one of the stack frames is inside mscorlib.dll // and we get location==null for it. It will trigger a NullReferenceException // if we don't skip over that stack frame. - if (location == null) - { + if (location == null) { continue; } @@ -288,7 +304,7 @@ namespace WsProxy { callFrames.Add (JObject.FromObject (new { functionName = method.Name, - + callFrameId = $"dotnet:scope:{frame_id}", functionLocation = method.StartLocation.ToJObject (), location = location.ToJObject (), @@ -300,25 +316,24 @@ namespace WsProxy { type = "local", @object = new { @type = "object", - className = "Object", + className = "Object", description = "Object", - objectId = $"dotnet:scope:{frame_id}" + objectId = $"dotnet:scope:{frame_id}", }, name = method.Name, startLocation = method.StartLocation.ToJObject (), endLocation = method.EndLocation.ToJObject (), - } - }, - - @this = new { - } + }}, + @this = new { } })); ++frame_id; this.current_callstack = frames; + } - } else if (!url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture)) { - callFrames.Add (f); + } else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture) + || url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) { + callFrames.Add (frame); } } @@ -393,6 +408,57 @@ namespace WsProxy { await SendCommand ("Debugger.resume", new JObject (), token); } + async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command) + { + var o = JObject.FromObject(new + { + expression = string.Format(command, object_id), + objectGroup = "mono_debugger", + includeCommandLineAPI = false, + silent = false, + returnByValue = true, + }); + + var res = await SendCommand("Runtime.evaluate", o, token); + + //if we fail we just buble that to the IDE (and let it panic over it) + if (res.IsErr) + { + SendResponse(msg_id, res, token); + return; + } + + var values = res.Value?["result"]?["value"]?.Values().ToArray(); + + var var_list = new List(); + + // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously + // results in a "Memory access out of bounds", causing 'values' to be null, + // so skip returning variable values in that case. + for (int i = 0; i < values.Length; i+=2) + { + string fieldName = (string)values[i]["name"]; + if (fieldName.Contains("k__BackingField")){ + fieldName = fieldName.Replace("k__BackingField", ""); + fieldName = fieldName.Replace("<", ""); + fieldName = fieldName.Replace(">", ""); + } + var_list.Add(JObject.FromObject(new + { + name = fieldName, + value = values[i+1]["value"] + })); + + } + o = JObject.FromObject(new + { + result = var_list + }); + + SendResponse(msg_id, Result.Ok(o), token); + } + + async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token) { var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id); @@ -425,6 +491,10 @@ namespace WsProxy { // results in a "Memory access out of bounds", causing 'values' to be null, // so skip returning variable values in that case. for (int i = 0; values != null && i < vars.Length; ++i) { + var value = values [i] ["value"]; + if (((string)value ["description"]) == null) + value ["description"] = value ["value"]?.ToString(); + var_list.Add (JObject.FromObject (new { name = vars [i].Name, value = values [i] ["value"] @@ -485,7 +555,8 @@ namespace WsProxy { url = s.Url, executionContextId = this.ctx_id, hash = s.DocHashCode, - executionContextAuxData = this.aux_ctx_data + executionContextAuxData = this.aux_ctx_data, + dotNetUrl = s.DotNetUrl }); //Debug ($"\tsending {s.Url}"); SendEvent ("Debugger.scriptParsed", ok, token); @@ -640,23 +711,51 @@ namespace WsProxy { } - void OnGetScriptSource (int msg_id, string script_id, CancellationToken token) + async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token) { var id = new SourceId (script_id); var src_file = store.GetFileById (id); var res = new StringWriter (); - res.WriteLine ($"//dotnet:{id}"); + //res.WriteLine ($"//{id}"); - using (var f = new StreamReader (File.Open (src_file.LocalPath, FileMode.Open))) { - res.Write (f.ReadToEnd ()); + try { + var uri = new Uri (src_file.Url); + if (uri.IsFile && File.Exists(uri.LocalPath)) { + using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) { + await res.WriteAsync (await f.ReadToEndAsync ()); + } + + var o = JObject.FromObject (new { + scriptSource = res.ToString () + }); + + SendResponse (msg_id, Result.Ok (o), token); + } else if(src_file.SourceLinkUri != null) { + var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri); + await res.WriteAsync (doc); + + var o = JObject.FromObject (new { + scriptSource = res.ToString () + }); + + SendResponse (msg_id, Result.Ok (o), token); + } else { + var o = JObject.FromObject (new { + scriptSource = $"// Unable to find document {src_file.SourceUri}" + }); + + SendResponse (msg_id, Result.Ok (o), token); + } + } catch (Exception e) { + var o = JObject.FromObject (new { + scriptSource = $"// Unable to read document ({e.Message})\n" + + $"Local path: {src_file?.SourceUri}\n" + + $"SourceLink path: {src_file?.SourceLinkUri}\n" + }); + + SendResponse (msg_id, Result.Ok (o), token); } - - var o = JObject.FromObject (new { - scriptSource = res.ToString () - }); - - SendResponse (msg_id, Result.Ok (o), token); } } } diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs index 0629491680..e575e0a244 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs @@ -144,7 +144,7 @@ namespace WsProxy { void Send (WebSocket to, JObject o, CancellationToken token) { - var bytes = Encoding.UTF8.GetBytes (o.ToString ()); + var bytes = Encoding.UTF8.GetBytes (o.ToString ()); var queue = GetQueueForSocket (to); var task = queue.Send (bytes, token); @@ -256,7 +256,7 @@ namespace WsProxy { } // , HttpContext context) - public async Task Run (Uri browserUri, WebSocket ideSocket) + public async Task Run (Uri browserUri, WebSocket ideSocket) { Debug ("wsproxy start"); using (this.ide = ideSocket) { @@ -276,7 +276,7 @@ namespace WsProxy { try { while (!x.IsCancellationRequested) { - var task = await Task.WhenAny (pending_ops); + var task = await Task.WhenAny (pending_ops.ToArray ()); //Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task)); if (task == pending_ops [0]) { var msg = ((Task)task).Result; From c2e2d7d1359ef8caf5460562adfe46b7f4f68df0 Mon Sep 17 00:00:00 2001 From: Stafford Williams Date: Fri, 24 May 2019 01:20:34 +1000 Subject: [PATCH 40/41] SignalR: Fix Crankier build (#9976) * fix benchmarkapps build * move Directory.* to BenchmarkServer --- .../benchmarkapps/{ => BenchmarkServer}/Directory.Build.props | 0 .../benchmarkapps/{ => BenchmarkServer}/Directory.Build.targets | 0 src/SignalR/perf/benchmarkapps/BenchmarkServer/Startup.cs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/SignalR/perf/benchmarkapps/{ => BenchmarkServer}/Directory.Build.props (100%) rename src/SignalR/perf/benchmarkapps/{ => BenchmarkServer}/Directory.Build.targets (100%) diff --git a/src/SignalR/perf/benchmarkapps/Directory.Build.props b/src/SignalR/perf/benchmarkapps/BenchmarkServer/Directory.Build.props similarity index 100% rename from src/SignalR/perf/benchmarkapps/Directory.Build.props rename to src/SignalR/perf/benchmarkapps/BenchmarkServer/Directory.Build.props diff --git a/src/SignalR/perf/benchmarkapps/Directory.Build.targets b/src/SignalR/perf/benchmarkapps/BenchmarkServer/Directory.Build.targets similarity index 100% rename from src/SignalR/perf/benchmarkapps/Directory.Build.targets rename to src/SignalR/perf/benchmarkapps/BenchmarkServer/Directory.Build.targets diff --git a/src/SignalR/perf/benchmarkapps/BenchmarkServer/Startup.cs b/src/SignalR/perf/benchmarkapps/BenchmarkServer/Startup.cs index 040e73ae0f..139ec09d47 100644 --- a/src/SignalR/perf/benchmarkapps/BenchmarkServer/Startup.cs +++ b/src/SignalR/perf/benchmarkapps/BenchmarkServer/Startup.cs @@ -33,7 +33,7 @@ namespace BenchmarkServer } } - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseSignalR(routes => { From 156c4feb655968d978d64ba065cab3dcc2121ae1 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 23 May 2019 09:00:39 -0700 Subject: [PATCH 41/41] Put request trailers in a separate collection (#10410) --- ...NetCore.Http.Abstractions.netcoreapp3.0.cs | 7 ++ .../Extensions/RequestTrailerExtensions.cs | 65 +++++++++++ ...AspNetCore.Http.Features.netstandard2.0.cs | 5 + .../src/IHttpRequestTrailersFeature.cs | 26 +++++ ...tCore.Server.Kestrel.Core.netcoreapp3.0.cs | 1 + src/Servers/Kestrel/Core/src/CoreStrings.resx | 3 + .../Http/Http1ChunkedEncodingMessageBody.cs | 5 +- .../Core/src/Internal/Http/Http1Connection.cs | 6 +- .../Http/Http1ContentLengthMessageBody.cs | 1 + .../src/Internal/Http/Http1MessageBody.cs | 2 + .../src/Internal/Http/Http1ParsingHandler.cs | 30 ++++- .../Http/HttpProtocol.FeatureCollection.cs | 15 +++ .../Internal/Http/HttpProtocol.Generated.cs | 23 ++++ .../Core/src/Internal/Http/HttpProtocol.cs | 23 ++++ .../src/Internal/Http/HttpRequestHeaders.cs | 11 +- .../src/Internal/Http2/Http2Connection.cs | 8 +- .../Core/src/Internal/Http2/Http2Stream.cs | 1 + .../Internal/Infrastructure/HttpUtilities.cs | 24 ++++ .../src/Properties/CoreStrings.Designer.cs | 14 +++ .../Kestrel/Core/test/Http1ConnectionTests.cs | 12 +- .../HttpProtocolFeatureCollectionTests.cs | 2 + ...Http1ConnectionParsingOverheadBenchmark.cs | 4 +- .../RequestParsingBenchmark.cs | 4 +- .../ChunkedRequestTests.cs | 104 ++++++++++++++++-- .../Http2/Http2ConnectionTests.cs | 14 ++- .../Http2/Http2TestBase.cs | 14 +++ .../MaxRequestBodySizeTests.cs | 3 +- .../HttpProtocolFeatureCollection.cs | 2 + 28 files changed, 383 insertions(+), 46 deletions(-) create mode 100644 src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs create mode 100644 src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs diff --git a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs index 899be43f0c..cae69dd66c 100644 --- a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs +++ b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs @@ -373,6 +373,13 @@ namespace Microsoft.AspNetCore.Http public string ToUriComponent() { throw null; } } public delegate System.Threading.Tasks.Task RequestDelegate(Microsoft.AspNetCore.Http.HttpContext context); + public static partial class RequestTrailerExtensions + { + public static bool CheckTrailersAvailable(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; } + public static Microsoft.Extensions.Primitives.StringValues GetDeclaredTrailers(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; } + public static Microsoft.Extensions.Primitives.StringValues GetTrailer(this Microsoft.AspNetCore.Http.HttpRequest request, string trailerName) { throw null; } + public static bool SupportsTrailers(this Microsoft.AspNetCore.Http.HttpRequest request) { throw null; } + } public static partial class ResponseTrailerExtensions { public static void AppendTrailer(this Microsoft.AspNetCore.Http.HttpResponse response, string trailerName, Microsoft.Extensions.Primitives.StringValues trailerValues) { } diff --git a/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs new file mode 100644 index 0000000000..6ffeb2eebc --- /dev/null +++ b/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs @@ -0,0 +1,65 @@ +// 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.Http.Features; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Http +{ + /// + /// HttpRequest extensions for working with request trailing headers. + /// + public static class RequestTrailerExtensions + { + /// + /// Gets the request "Trailer" header that lists which trailers to expect after the body. + /// + /// + /// + public static StringValues GetDeclaredTrailers(this HttpRequest request) + { + return request.Headers.GetCommaSeparatedValues(HeaderNames.Trailer); + } + + /// + /// Indicates if the request supports receiving trailer headers. + /// + /// + /// + public static bool SupportsTrailers(this HttpRequest request) + { + return request.HttpContext.Features.Get() != null; + } + + /// + /// Checks if the request supports trailers and they are available to be read now. + /// This does not mean that there are any trailers to read. + /// + /// + /// + public static bool CheckTrailersAvailable(this HttpRequest request) + { + return request.HttpContext.Features.Get()?.Available == true; + } + + /// + /// Gets the requested trailing header from the response. Check + /// or a NotSupportedException may be thrown. + /// Check or an InvalidOperationException may be thrown. + /// + /// + /// + public static StringValues GetTrailer(this HttpRequest request, string trailerName) + { + var feature = request.HttpContext.Features.Get(); + if (feature == null) + { + throw new NotSupportedException("This request does not support trailers."); + } + + return feature.Trailers[trailerName]; + } + } +} diff --git a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs index 48f381bb96..8c0f10b5dc 100644 --- a/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs +++ b/src/Http/Http.Features/ref/Microsoft.AspNetCore.Http.Features.netstandard2.0.cs @@ -195,6 +195,11 @@ namespace Microsoft.AspNetCore.Http.Features System.Threading.CancellationToken RequestAborted { get; set; } void Abort(); } + public partial interface IHttpRequestTrailersFeature + { + bool Available { get; } + Microsoft.AspNetCore.Http.IHeaderDictionary Trailers { get; } + } public partial interface IHttpResponseFeature { System.IO.Stream Body { get; set; } diff --git a/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs b/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs new file mode 100644 index 0000000000..19706e9e4e --- /dev/null +++ b/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs @@ -0,0 +1,26 @@ +// 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.Http.Features +{ + /// + /// This feature exposes HTTP request trailer headers, either for HTTP/1.1 chunked bodies or HTTP/2 trailing headers. + /// + public interface IHttpRequestTrailersFeature + { + /// + /// Indicates if the are available yet. They may not be available until the + /// request body is fully read. + /// + bool Available { get; } + + /// + /// The trailing headers received. This will throw if + /// returns false. They may not be available until the request body is fully read. If there are no trailers this will + /// return an empty collection. + /// + IHeaderDictionary Trailers { get; } + } +} 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 8aa67fc8e9..1c23c2dc98 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 @@ -276,6 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure public static string GetAsciiOrUTF8StringNonNullCharacters(this System.Span span) { throw null; } public static string GetAsciiStringEscaped(this System.Span span, int maxChars) { throw null; } public static string GetAsciiStringNonNullCharacters(this System.Span span) { throw null; } + public static string GetHeaderName(this System.Span span) { throw null; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownHttpScheme(this System.Span span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpScheme knownScheme) { throw null; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]public static bool GetKnownMethod(this System.Span span, out Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod method, out int length) { throw null; } public static Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod GetKnownMethod(string value) { throw null; } diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 4b41f63c44..7ae053cb15 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -602,4 +602,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l This feature is not supported for HTTP/2 requests except to disable it entirely by setting the rate to null. + + The request trailers are not available yet. They may not be available until the full request body is read. + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs index fdc8edb0cf..7c1327cd73 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs @@ -35,7 +35,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http : base(context) { RequestKeepAlive = keepAlive; - _requestBodyPipe = CreateRequestBodyPipe(context); } @@ -301,7 +300,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http // _consumedBytes aren't tracked for trailer headers, since headers have separate limits. if (_mode == Mode.TrailerHeaders) { - if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + if (_context.TakeMessageHeaders(readableBuffer, trailers: true, out consumed, out examined)) { _mode = Mode.Complete; } @@ -489,6 +488,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http consumed = trailerBuffer.End; AddAndCheckConsumedBytes(2); _mode = Mode.Complete; + // No trailers + _context.OnTrailersComplete(); } else { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index d657d70ee0..6a54f47e28 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http break; } case RequestProcessingStatus.ParsingHeaders: - if (TakeMessageHeaders(buffer, out consumed, out examined)) + if (TakeMessageHeaders(buffer, trailers: false, out consumed, out examined)) { _requestProcessingStatus = RequestProcessingStatus.AppStarted; } @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return result; } - public bool TakeMessageHeaders(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) + public bool TakeMessageHeaders(ReadOnlySequence buffer, bool trailers, out SequencePosition consumed, out SequencePosition examined) { // Make sure the buffer is limited bool overLength = false; @@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http overLength = true; } - var result = _parser.ParseHeaders(new Http1ParsingHandler(this), buffer, out consumed, out examined, out var consumedBytes); + var result = _parser.ParseHeaders(new Http1ParsingHandler(this, trailers), buffer, out consumed, out examined, out var consumedBytes); _remainingRequestHeadersBytesAllowed -= consumedBytes; if (!result && overLength) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs index 66d97819ba..2d13e68680 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs @@ -212,6 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _context.Input.AdvanceTo(consumed); _finalAdvanceCalled = true; + _context.OnTrailersComplete(); } return; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index a661946a62..0691c842d7 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload); } + context.OnTrailersComplete(); // No trailers for these. return new Http1UpgradeMessageBody(context); } @@ -173,6 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http BadHttpRequestException.Throw(requestRejectionReason, context.Method); } + context.OnTrailersComplete(); // No trailers for these. return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs index bcc905cab9..b4c67c13a3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1ParsingHandler.cs @@ -8,17 +8,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http internal readonly struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler { public readonly Http1Connection Connection; + public readonly bool Trailers; public Http1ParsingHandler(Http1Connection connection) { Connection = connection; + Trailers = false; + } + + public Http1ParsingHandler(Http1Connection connection, bool trailers) + { + Connection = connection; + Trailers = trailers; } public void OnHeader(Span name, Span value) - => Connection.OnHeader(name, value); + { + if (Trailers) + { + Connection.OnTrailer(name, value); + } + else + { + Connection.OnHeader(name, value); + } + } public void OnHeadersComplete() - => Connection.OnHeadersComplete(); + { + if (Trailers) + { + Connection.OnTrailersComplete(); + } + else + { + Connection.OnHeadersComplete(); + } + } public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) => Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs index 637d6e086f..11fcadc074 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http IHttpConnectionFeature, IHttpRequestLifetimeFeature, IHttpRequestIdentifierFeature, + IHttpRequestTrailersFeature, IHttpBodyControlFeature, IHttpMaxRequestBodySizeFeature, IHttpResponseStartFeature, @@ -133,6 +134,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } + bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable; + + IHeaderDictionary IHttpRequestTrailersFeature.Trailers + { + get + { + if (!RequestTrailersAvailable) + { + throw new InvalidOperationException(CoreStrings.RequestTrailersNotAvailable); + } + return RequestTrailers; + } + } + int IHttpResponseFeature.StatusCode { get => StatusCode; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs index 8ba4e4b050..b9b3e26905 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private static readonly Type IRouteValuesFeatureType = typeof(IRouteValuesFeature); private static readonly Type IEndpointFeatureType = typeof(IEndpointFeature); private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature); + private static readonly Type IHttpRequestTrailersFeatureType = typeof(IHttpRequestTrailersFeature); private static readonly Type IQueryFeatureType = typeof(IQueryFeature); private static readonly Type IFormFeatureType = typeof(IFormFeature); private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature); @@ -52,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private object _currentIRouteValuesFeature; private object _currentIEndpointFeature; private object _currentIHttpAuthenticationFeature; + private object _currentIHttpRequestTrailersFeature; private object _currentIQueryFeature; private object _currentIFormFeature; private object _currentIHttpUpgradeFeature; @@ -82,6 +84,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _currentIHttpUpgradeFeature = this; _currentIHttpRequestIdentifierFeature = this; _currentIHttpRequestLifetimeFeature = this; + _currentIHttpRequestTrailersFeature = this; _currentIHttpConnectionFeature = this; _currentIHttpMaxRequestBodySizeFeature = this; _currentIHttpMinRequestBodyDataRateFeature = this; @@ -201,6 +204,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { feature = _currentIHttpAuthenticationFeature; } + else if (key == IHttpRequestTrailersFeatureType) + { + feature = _currentIHttpRequestTrailersFeature; + } else if (key == IQueryFeatureType) { feature = _currentIQueryFeature; @@ -321,6 +328,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _currentIHttpAuthenticationFeature = value; } + else if (key == IHttpRequestTrailersFeatureType) + { + _currentIHttpRequestTrailersFeature = value; + } else if (key == IQueryFeatureType) { _currentIQueryFeature = value; @@ -439,6 +450,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { feature = (TFeature)_currentIHttpAuthenticationFeature; } + else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature)) + { + feature = (TFeature)_currentIHttpRequestTrailersFeature; + } else if (typeof(TFeature) == typeof(IQueryFeature)) { feature = (TFeature)_currentIQueryFeature; @@ -563,6 +578,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _currentIHttpAuthenticationFeature = feature; } + else if (typeof(TFeature) == typeof(IHttpRequestTrailersFeature)) + { + _currentIHttpRequestTrailersFeature = feature; + } else if (typeof(TFeature) == typeof(IQueryFeature)) { _currentIQueryFeature = feature; @@ -679,6 +698,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature); } + if (_currentIHttpRequestTrailersFeature != null) + { + yield return new KeyValuePair(IHttpRequestTrailersFeatureType, _currentIHttpRequestTrailersFeature); + } if (_currentIQueryFeature != null) { yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index 78701c5571..ea8e77cd3b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -206,6 +206,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } public IHeaderDictionary RequestHeaders { get; set; } + public IHeaderDictionary RequestTrailers { get; } = new HeaderDictionary(); + public bool RequestTrailersAvailable { get; set; } public Stream RequestBody { get; set; } public PipeReader RequestBodyPipeReader { get; set; } @@ -369,6 +371,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http HttpResponseHeaders.Reset(); RequestHeaders = HttpRequestHeaders; ResponseHeaders = HttpResponseHeaders; + RequestTrailers.Clear(); + RequestTrailersAvailable = false; _isLeasedMemoryInvalid = true; _hasAdvanced = false; @@ -524,11 +528,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http HttpRequestHeaders.Append(name, value); } + public void OnTrailer(Span name, Span value) + { + // Trailers still count towards the limit. + _requestHeadersParsed++; + if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) + { + BadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders); + } + + string key = name.GetHeaderName(); + var valueStr = value.GetAsciiOrUTF8StringNonNullCharacters(); + RequestTrailers.Append(key, valueStr); + } + public void OnHeadersComplete() { HttpRequestHeaders.OnHeadersComplete(); } + public void OnTrailersComplete() + { + RequestTrailersAvailable = true; + } + public async Task ProcessRequestsAsync(IHttpApplication application) { try diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs index ea3adc24ba..a6bf25ecbb 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs @@ -103,16 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http [MethodImpl(MethodImplOptions.NoInlining)] private unsafe void AppendUnknownHeaders(Span name, string valueString) { - string key = new string('\0', name.Length); - fixed (byte* pKeyBytes = name) - fixed (char* keyBuffer = key) - { - if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, name.Length)) - { - BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName); - } - } - + string key = name.GetHeaderName(); Unknown.TryGetValue(key, out var existing); Unknown[key] = AppendValue(existing, valueString); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index fed15bdf83..fd5728b8e2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1073,9 +1073,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 ValidateHeader(name, value); try { - // Drop trailers for now. Adding them to the request headers is not thread safe. - // https://github.com/aspnet/KestrelHttpServer/issues/2051 - if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers) + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + { + _currentHeadersStream.OnTrailer(name, value); + } + else { // Throws BadRequest for header count limit breaches. // Throws InvalidOperation for bad encoding. diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index dfefd1a3d1..5a27a5641f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -403,6 +403,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } + OnTrailersComplete(); RequestBodyPipe.Writer.Complete(); _inputFlowControl.StopWindowUpdates(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs index 7c5276259e..5c266d85df 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HttpUtilities.cs @@ -84,6 +84,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure } } + // The same as GetAsciiStringNonNullCharacters but throws BadRequest + public static unsafe string GetHeaderName(this Span span) + { + if (span.IsEmpty) + { + return string.Empty; + } + + var asciiString = new string('\0', span.Length); + + fixed (char* output = asciiString) + fixed (byte* buffer = span) + { + // This version if AsciiUtilities returns null if there are any null (0 byte) characters + // in the string + if (!StringUtilities.TryGetAsciiString(buffer, output, span.Length)) + { + BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName); + } + } + + return asciiString; + } + public static unsafe string GetAsciiStringNonNullCharacters(this Span span) { if (span.IsEmpty) diff --git a/src/Servers/Kestrel/Core/src/Properties/CoreStrings.Designer.cs b/src/Servers/Kestrel/Core/src/Properties/CoreStrings.Designer.cs index a63a15c259..80f4cdecd6 100644 --- a/src/Servers/Kestrel/Core/src/Properties/CoreStrings.Designer.cs +++ b/src/Servers/Kestrel/Core/src/Properties/CoreStrings.Designer.cs @@ -2268,6 +2268,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core internal static string FormatHttp2MinDataRateNotSupported() => GetString("Http2MinDataRateNotSupported"); + /// + /// The request trailers are not available yet. They may not be available until the full request body is read. + /// + internal static string RequestTrailersNotAvailable + { + get => GetString("RequestTrailersNotAvailable"); + } + + /// + /// The request trailers are not available yet. They may not be available until the full request body is read. + /// + internal static string FormatRequestTrailersNotAvailable() + => GetString("RequestTrailersNotAvailable"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs index 4c3cb758e9..494e4531e6 100644 --- a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.UTF8.GetBytes("\r\n\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); + _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined); _transport.Input.AdvanceTo(_consumed, _examined); Assert.Equal(headerValue, _http1Connection.RequestHeaders[headerName]); @@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes("\r\n\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined)); } [Fact] @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined)); _transport.Input.AdvanceTo(_consumed, _examined); Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message); @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined)); + var exception = Assert.Throws(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined)); _transport.Input.AdvanceTo(_consumed, _examined); Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message); @@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n")); var readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); + var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined); _transport.Input.AdvanceTo(_consumed, _examined); Assert.True(takeMessageHeaders); @@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n")); readableBuffer = (await _transport.Input.ReadAsync()).Buffer; - takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, out _consumed, out _examined); + takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined); _transport.Input.AdvanceTo(_consumed, _examined); Assert.True(takeMessageHeaders); diff --git a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs index c8fb802885..53f5cfcc1e 100644 --- a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs @@ -121,6 +121,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpRequestTrailersFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection(); @@ -144,6 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _collection.Set(CreateHttp1Connection()); _collection.Set(CreateHttp1Connection()); _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); _collection.Set(CreateHttp1Connection()); _collection.Set(CreateHttp1Connection()); _collection.Set(CreateHttp1Connection()); diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs index 75bc420343..84ac57e630 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance ErrorUtilities.ThrowInvalidRequestLine(); } - if (!_http1Connection.TakeMessageHeaders(_buffer, out consumed, out examined)) + if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { _http1Connection.Reset(); - if (!_http1Connection.TakeMessageHeaders(_buffer, out var consumed, out var examined)) + if (!_http1Connection.TakeMessageHeaders(_buffer, trailers: false, out var consumed, out var examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } diff --git a/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs index 33ae90daeb..da9f0f7faf 100644 --- a/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Kestrel.Performance/RequestParsingBenchmark.cs @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance readableBuffer = readableBuffer.Slice(consumed); - if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } @@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); readableBuffer = result.Buffer; - if (!Http1Connection.TakeMessageHeaders(readableBuffer, out consumed, out examined)) + if (!Http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out consumed, out examined)) { ErrorUtilities.ThrowInvalidRequestHeaders(); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs index 7247e11226..2f3f01a941 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedRequestTests.cs @@ -232,18 +232,59 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests var buffer = new byte[200]; + // The first request is chunked with no trailers. + if (requestsReceived == 0) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); // Not yet + } + // The middle requests are chunked with trailers. + else if (requestsReceived < requestCount) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); // Not yet + Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString()); + } + // The last request is content-length with no trailers. + else + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); + } + while (await request.Body.ReadAsync(buffer, 0, buffer.Length) != 0) { ;// read to end } - if (requestsReceived < requestCount) + Assert.False(request.Headers.ContainsKey("X-Trailer-Header")); + + // The first request is chunked with no trailers. + if (requestsReceived == 0) { - Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString()); + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString()); + Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString()); } + // The middle requests are chunked with trailers. + else if (requestsReceived < requestCount) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString()); + Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString()); + } + // The last request is content-length with no trailers. else { - Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"])); + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString()); + Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString()); } requestsReceived++; @@ -278,6 +319,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked", + "Trailer: X-Trailer-Header", "", "C", $"HelloChunk{i:00}", @@ -315,6 +357,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests var response = httpContext.Response; var request = httpContext.Request; + // The first request is chunked with no trailers. + if (requestsReceived == 0) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); // Not yet + } + // The middle requests are chunked with trailers. + else if (requestsReceived < requestCount) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); // Not yet + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); // Not yet + Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString()); + } + // The last request is content-length with no trailers. + else + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Throws(() => request.GetTrailer("X-Trailer-Header")); + } + while (true) { var result = await request.BodyReader.ReadAsync(); @@ -325,13 +390,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } - if (requestsReceived < requestCount) + Assert.False(request.Headers.ContainsKey("X-Trailer-Header")); + + // The first request is chunked with no trailers. + if (requestsReceived == 0) { - Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString()); + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString()); + Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString()); } + // The middle requests are chunked with trailers. + else if (requestsReceived < requestCount) + { + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal("X-Trailer-Header", request.GetDeclaredTrailers().ToString()); + Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString()); + } + // The last request is content-length with no trailers. else { - Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"])); + Assert.True(request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(request.CheckTrailersAvailable(), "CheckTrailersAvailable"); + Assert.Equal(string.Empty, request.GetDeclaredTrailers().ToString()); + Assert.Equal(string.Empty, request.GetTrailer("X-Trailer-Header").ToString()); } requestsReceived++; @@ -366,6 +449,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests "POST / HTTP/1.1", "Host:", "Transfer-Encoding: chunked", + "Trailer: X-Trailer-Header", "", "C", $"HelloChunk{i:00}", @@ -495,13 +579,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests ;// read to end } + Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"])); + if (requestsReceived < requestCount) { - Assert.Equal(new string('a', requestsReceived), request.Headers["X-Trailer-Header"].ToString()); - } - else - { - Assert.True(string.IsNullOrEmpty(request.Headers["X-Trailer-Header"])); + Assert.Equal(new string('a', requestsReceived), request.GetTrailer("X-Trailer-Header").ToString()); } requestsReceived++; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 2081227840..593f3bc644 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -1174,7 +1174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Theory] [InlineData(true)] [InlineData(false)] - public async Task HEADERS_Received_WithTrailers_Discarded(bool sendData) + public async Task HEADERS_Received_WithTrailers_Available(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); @@ -1206,10 +1206,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests VerifyDecodedRequestHeaders(_browserRequestHeaders); - // Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630 + // Make sure the trailers are in the trailers collection. foreach (var header in _requestTrailers) { Assert.False(_receivedHeaders.ContainsKey(header.Key)); + Assert.True(_receivedTrailers.ContainsKey(header.Key)); + Assert.Equal(header.Value, _receivedTrailers[header.Key]); } await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); @@ -3288,7 +3290,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Theory] [InlineData(true)] [InlineData(false)] - public async Task CONTINUATION_Received_WithTrailers_Discarded(bool sendData) + public async Task CONTINUATION_Received_WithTrailers_Available(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); @@ -3331,9 +3333,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests VerifyDecodedRequestHeaders(_browserRequestHeaders); - // Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630 + // Make sure the trailers are in the trailers collection. Assert.False(_receivedHeaders.ContainsKey("trailer-1")); Assert.False(_receivedHeaders.ContainsKey("trailer-2")); + Assert.True(_receivedTrailers.ContainsKey("trailer-1")); + Assert.True(_receivedTrailers.ContainsKey("trailer-2")); + Assert.Equal("1", _receivedTrailers["trailer-1"]); + Assert.Equal("2", _receivedTrailers["trailer-2"]); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 3e024a9bce..6751565db1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -130,6 +130,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests protected readonly ConcurrentDictionary> _runningStreams = new ConcurrentDictionary>(); protected readonly Dictionary _receivedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + protected readonly Dictionary _receivedTrailers = new Dictionary(StringComparer.OrdinalIgnoreCase); protected readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); protected readonly HashSet _abortedStreamIds = new HashSet(); protected readonly object _abortedStreamIdsLock = new object(); @@ -199,16 +200,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _readTrailersApplication = async context => { + Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers"); + Assert.False(context.Request.CheckTrailersAvailable(), "SupportsTrailers"); + using (var ms = new MemoryStream()) { // Consuming the entire request body guarantees trailers will be available await context.Request.Body.CopyToAsync(ms); } + Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers"); + Assert.True(context.Request.CheckTrailersAvailable(), "SupportsTrailers"); + foreach (var header in context.Request.Headers) { _receivedHeaders[header.Key] = header.Value.ToString(); } + + var trailers = context.Features.Get().Trailers; + + foreach (var header in trailers) + { + _receivedTrailers[header.Key] = header.Value.ToString(); + } }; _bufferingApplication = async context => diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs index 6feeeef83a..ef5e141063 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/MaxRequestBodySizeTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; @@ -360,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } while (count != 0); Assert.Equal("Hello World", Encoding.ASCII.GetString(buffer)); - Assert.Equal("trailing-value", context.Request.Headers["Trailing-Header"].ToString()); + Assert.Equal("trailing-value", context.Request.GetTrailer("Trailing-Header").ToString()); }, new TestServiceContext(LoggerFactory) { ServerOptions = { Limits = { MaxRequestBodySize = globalMaxRequestBodySize } } })) { diff --git a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs index c8bd5f3d3e..8266770719 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -26,6 +26,7 @@ namespace CodeGenerator var commonFeatures = new[] { "IHttpAuthenticationFeature", + "IHttpRequestTrailersFeature", "IQueryFeature", "IFormFeature", }; @@ -69,6 +70,7 @@ namespace CodeGenerator "IHttpUpgradeFeature", "IHttpRequestIdentifierFeature", "IHttpRequestLifetimeFeature", + "IHttpRequestTrailersFeature", "IHttpConnectionFeature", "IHttpMaxRequestBodySizeFeature", "IHttpMinRequestBodyDataRateFeature",