diff --git a/src/Security/.gitignore b/src/Security/.gitignore index d5717b3f3f..3d7e16e84a 100644 --- a/src/Security/.gitignore +++ b/src/Security/.gitignore @@ -30,3 +30,4 @@ project.lock.json /.vs/ .vscode/ global.json +BenchmarkDotNet.Artifacts/ diff --git a/src/Security/Directory.Build.targets b/src/Security/Directory.Build.targets index 78626b773e..7e3f8df92e 100644 --- a/src/Security/Directory.Build.targets +++ b/src/Security/Directory.Build.targets @@ -1,10 +1,6 @@ - $(MicrosoftNETCoreApp20PackageVersion) - $(MicrosoftNETCoreApp21PackageVersion) - $(MicrosoftNETCoreApp22PackageVersion) + $(MicrosoftNETCoreAppPackageVersion) $(NETStandardLibrary20PackageVersion) - - 99.9 diff --git a/src/Security/README.md b/src/Security/README.md index e8e64c2936..66c2aa45e8 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -1,9 +1,7 @@ -ASP.NET Security -======== +ASP.NET Security [Archived] +=========================== -AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/fujhh8n956v5ohfd/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/Security/branch/dev) - -Travis: [![Travis](https://travis-ci.org/aspnet/Security.svg?branch=dev)](https://travis-ci.org/aspnet/Security) +**This GitHub project has been archived.** Ongoing development on this project can be found in . Contains the security and authorization middlewares for ASP.NET Core. @@ -14,4 +12,4 @@ A list of community projects related to authentication and security for ASP.NET ASP.NET Security will not include Basic Authentication middleware due to its potential insecurity and performance problems. If you host under IIS you can enable it via IIS configuration. -This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. +This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. diff --git a/src/Security/Security.sln b/src/Security/Security.sln index 3df759651b..240100e8c4 100644 --- a/src/Security/Security.sln +++ b/src/Security/Security.sln @@ -79,6 +79,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WsFedSample", "samples\WsFedSample\WsFedSample.csproj", "{5EC2E398-E46A-430D-8E4B-E91C8FC3E800}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Security.Performance", "benchmarks\Microsoft.AspNetCore.Security.Performance\Microsoft.AspNetCore.Security.Performance.csproj", "{556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -517,6 +521,22 @@ Global {5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x64.Build.0 = Release|Any CPU {5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.ActiveCfg = Release|Any CPU {5EC2E398-E46A-430D-8E4B-E91C8FC3E800}.Release|x86.Build.0 = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x64.Build.0 = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Debug|x86.Build.0 = Debug|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Any CPU.Build.0 = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.ActiveCfg = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x64.Build.0 = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.ActiveCfg = Release|Any CPU + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -549,6 +569,7 @@ Global {24A28F5D-E5A9-4CA8-B0D2-924A1F8BE14E} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF} {B1FC6AAF-9BF2-4CDA-84A2-AA8BF7603F29} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} {5EC2E398-E46A-430D-8E4B-E91C8FC3E800} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF} + {556C4FAA-F4B1-4EA9-8921-CB1DF7D94C2A} = {55052FE3-F8C2-4E6C-97B0-C02ED1DBEA62} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ABF8089E-43D0-4010-84A7-7A9DCFE49357} diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationMiddlewareBenchmark.cs b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationMiddlewareBenchmark.cs new file mode 100644 index 0000000000..90cb135de5 --- /dev/null +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationMiddlewareBenchmark.cs @@ -0,0 +1,53 @@ +// 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; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Security +{ + public class AuthorizationMiddlewareBenchmark + { + private AuthorizationMiddleware _authorizationMiddleware; + private DefaultHttpContext _httpContextNoEndpoint; + private DefaultHttpContext _httpContextHasEndpoint; + + [GlobalSetup] + public void Setup() + { + var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())); + _authorizationMiddleware = new AuthorizationMiddleware((context) => Task.CompletedTask, policyProvider); + + _httpContextNoEndpoint = new DefaultHttpContext(); + + var feature = new EndpointFeature + { + Endpoint = new Endpoint((context) => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint") + }; + _httpContextHasEndpoint = new DefaultHttpContext(); + _httpContextHasEndpoint.Features.Set(feature); + } + + [Benchmark] + public Task Invoke_NoEndpoint_NoAuthorization() + { + return _authorizationMiddleware.Invoke(_httpContextNoEndpoint); + } + + [Benchmark] + public Task Invoke_HasEndpoint_NoAuthorization() + { + return _authorizationMiddleware.Invoke(_httpContextHasEndpoint); + } + + private class EndpointFeature : IEndpointFeature + { + public Endpoint Endpoint { get; set; } + } + } +} diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationPolicyBenchmark.cs b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationPolicyBenchmark.cs new file mode 100644 index 0000000000..86afdcd5ae --- /dev/null +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/AuthorizationPolicyBenchmark.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Security +{ + public class AuthorizationPolicyBenchmark + { + private DefaultAuthorizationPolicyProvider _policyProvider; + + [GlobalSetup] + public void Setup() + { + _policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())); + } + + [Benchmark] + public Task CombineAsync() + { + return AuthorizationPolicy.CombineAsync(_policyProvider, Array.Empty()); + } + } +} diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj new file mode 100644 index 0000000000..2f16dd6699 --- /dev/null +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Microsoft.AspNetCore.Security.Performance.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.0 + Exe + true + true + false + Microsoft.AspNetCore.Security + + + + + + + + + + + + + + + diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Properties/AssemblyInfo.cs b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cfb0b19c2e --- /dev/null +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/Properties/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark] \ No newline at end of file diff --git a/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/readme.md b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/readme.md new file mode 100644 index 0000000000..cbe60261b3 --- /dev/null +++ b/src/Security/benchmarks/Microsoft.AspNetCore.Security.Performance/readme.md @@ -0,0 +1,16 @@ +Compile the solution in Release mode (so binaries are available in release) + +To run a specific benchmark add it as parameter. +``` +dotnet run -c Release --framework +``` + +To run all benchmarks use '*' as the name. +``` +dotnet run -c Release --framework * +``` + +If you run without any parameters, you'll be offered the list of all benchmarks and get to choose. +``` +dotnet run -c Release --framework +``` \ No newline at end of file diff --git a/src/Security/build/dependencies.props b/src/Security/build/dependencies.props index 0d1a80ebb6..163a3d4004 100644 --- a/src/Security/build/dependencies.props +++ b/src/Security/build/dependencies.props @@ -3,50 +3,54 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview2-20181004.6 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 3.14.2 - 5.2.0 - 5.2.0 - 2.0.9 - 2.1.3 - 2.2.0-preview3-27001-02 + 0.10.13 + 3.0.0-build-20181114.5 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-preview-181113-11 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-alpha1-10742 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.0.0-preview-181113-11 + 3.19.8 + 5.3.0 + 5.3.0 + 3.0.0-preview1-26907-05 15.6.1 3.0.1 3.0.1 3.0.1 + 4.10.0 2.0.3 11.0.2 - 5.2.0 + 5.3.0 0.10.0 2.3.1 2.4.0 + diff --git a/src/Security/build/repo.props b/src/Security/build/repo.props index 077c753e69..4402da1d0d 100644 --- a/src/Security/build/repo.props +++ b/src/Security/build/repo.props @@ -1,19 +1,13 @@ - - - Internal.AspNetCore.Universe.Lineup - 2.2.0-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json - - - + diff --git a/src/Security/samples/CookiePolicySample/CookiePolicySample.csproj b/src/Security/samples/CookiePolicySample/CookiePolicySample.csproj index 48227eb07c..010edb018e 100644 --- a/src/Security/samples/CookiePolicySample/CookiePolicySample.csproj +++ b/src/Security/samples/CookiePolicySample/CookiePolicySample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 diff --git a/src/Security/samples/CookieSample/CookieSample.csproj b/src/Security/samples/CookieSample/CookieSample.csproj index 1029f18193..bc92d68a21 100644 --- a/src/Security/samples/CookieSample/CookieSample.csproj +++ b/src/Security/samples/CookieSample/CookieSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 diff --git a/src/Security/samples/CookieSessionSample/CookieSessionSample.csproj b/src/Security/samples/CookieSessionSample/CookieSessionSample.csproj index 19bd043746..6230c98a53 100644 --- a/src/Security/samples/CookieSessionSample/CookieSessionSample.csproj +++ b/src/Security/samples/CookieSessionSample/CookieSessionSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 diff --git a/src/Security/samples/JwtBearerSample/JwtBearerSample.csproj b/src/Security/samples/JwtBearerSample/JwtBearerSample.csproj index 3e0192a233..58bf5e2d97 100644 --- a/src/Security/samples/JwtBearerSample/JwtBearerSample.csproj +++ b/src/Security/samples/JwtBearerSample/JwtBearerSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 aspnet5-JwtBearerSample-20151210102827 diff --git a/src/Security/samples/JwtBearerSample/wwwroot/index.html b/src/Security/samples/JwtBearerSample/wwwroot/index.html index f71dccb693..4715ebd42a 100644 --- a/src/Security/samples/JwtBearerSample/wwwroot/index.html +++ b/src/Security/samples/JwtBearerSample/wwwroot/index.html @@ -64,5 +64,6 @@ + diff --git a/src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj b/src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj index 2abd57cd89..61754b60bf 100644 --- a/src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj +++ b/src/Security/samples/OpenIdConnect.AzureAdSample/OpenIdConnect.AzureAdSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 aspnet5-OpenIdConnectSample-20151210110318 diff --git a/src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj b/src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj index bb02ef1595..137ee47ba2 100644 --- a/src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj +++ b/src/Security/samples/OpenIdConnectSample/OpenIdConnectSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 aspnet5-OpenIdConnectSample-20151210110318 diff --git a/src/Security/samples/OpenIdConnectSample/Startup.cs b/src/Security/samples/OpenIdConnectSample/Startup.cs index 1aa7625cb0..a2b5a9a5ca 100644 --- a/src/Security/samples/OpenIdConnectSample/Startup.cs +++ b/src/Security/samples/OpenIdConnectSample/Startup.cs @@ -63,6 +63,7 @@ namespace OpenIdConnectSample o.ResponseType = OpenIdConnectResponseType.CodeIdToken; o.SaveTokens = true; o.GetClaimsFromUserInfoEndpoint = true; + o.AccessDeniedPath = "/access-denied-from-remote"; o.ClaimActions.MapAllExcept("aud", "iss", "iat", "nbf", "exp", "aio", "c_hash", "uti", "nonce"); @@ -126,6 +127,16 @@ namespace OpenIdConnectSample return; } + if (context.Request.Path.Equals("/access-denied-from-remote")) + { + await WriteHtmlAsync(response, async res => + { + await res.WriteAsync($"

Access Denied error received from the remote authorization server

"); + await res.WriteAsync("Home"); + }); + return; + } + if (context.Request.Path.Equals("/Account/AccessDenied")) { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); diff --git a/src/Security/samples/SocialSample/SocialSample.csproj b/src/Security/samples/SocialSample/SocialSample.csproj index a08b9799d1..e1e2262b0d 100644 --- a/src/Security/samples/SocialSample/SocialSample.csproj +++ b/src/Security/samples/SocialSample/SocialSample.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2 + netcoreapp3.0 aspnet5-SocialSample-20151210111056 diff --git a/src/Security/samples/WsFedSample/WsFedSample.csproj b/src/Security/samples/WsFedSample/WsFedSample.csproj index 4fb38fa3e3..e1191ac811 100644 --- a/src/Security/samples/WsFedSample/WsFedSample.csproj +++ b/src/Security/samples/WsFedSample/WsFedSample.csproj @@ -1,7 +1,7 @@ - net461 + netcoreapp3.0 diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs index b77a51ef4f..ff6bb3cbca 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies var returnUrl = properties.RedirectUri; if (string.IsNullOrEmpty(returnUrl)) { - returnUrl = OriginalPathBase + Request.Path + Request.QueryString; + returnUrl = OriginalPathBase + OriginalPath + Request.QueryString; } var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl); var redirectContext = new RedirectContext(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri)); @@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies var redirectUri = properties.RedirectUri; if (string.IsNullOrEmpty(redirectUri)) { - redirectUri = OriginalPathBase + Request.Path + Request.QueryString; + redirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri); diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs index 35017f9c4d..78156caecc 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs @@ -147,68 +147,5 @@ namespace Microsoft.AspNetCore.Authentication.Cookies /// /// public TimeSpan ExpireTimeSpan { get; set; } - - #region Obsolete API - /// - /// - /// This property is obsolete and will be removed in a future version. The recommended alternative is on . - /// - /// - /// Determines the cookie name used to persist the identity. The default value is ".AspNetCore.Cookies". - /// This value should be changed if you change the name of the AuthenticationScheme, especially if your - /// system uses the cookie authentication handler multiple times. - /// - /// - [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Name) + ".")] - public string CookieName { get => Cookie.Name; set => Cookie.Name = value; } - - /// - /// - /// This property is obsolete and will be removed in a future version. The recommended alternative is on . - /// - /// - /// Determines the domain used to create the cookie. Is not provided by default. - /// - /// - [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Domain) + ".")] - public string CookieDomain { get => Cookie.Domain; set => Cookie.Domain = value; } - - /// - /// - /// This property is obsolete and will be removed in a future version. The recommended alternative is on . - /// - /// - /// Determines the path used to create the cookie. The default value is "/" for highest browser compatibility. - /// - /// - [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.Path) + ".")] - public string CookiePath { get => Cookie.Path; set => Cookie.Path = value; } - - /// - /// - /// This property is obsolete and will be removed in a future version. The recommended alternative is on . - /// - /// - /// Determines if the browser should allow the cookie to be accessed by client-side javascript. The - /// default is true, which means the cookie will only be passed to http requests and is not made available - /// to script on the page. - /// - /// - [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.HttpOnly) + ".")] - public bool CookieHttpOnly { get => Cookie.HttpOnly; set => Cookie.HttpOnly = value; } - - /// - /// - /// This property is obsolete and will be removed in a future version. The recommended alternative is on . - /// - /// - /// Determines if the cookie should only be transmitted on HTTPS request. The default is to limit the cookie - /// to HTTPS requests if the page which is doing the SignIn is also HTTPS. If you have an HTTPS sign in page - /// and portions of your site are HTTP you may need to change this value. - /// - /// - [Obsolete("This property is obsolete and will be removed in a future version. The recommended alternative is " + nameof(Cookie) + "." + nameof(CookieBuilder.SecurePolicy) + ".")] - public CookieSecurePolicy CookieSecure { get => Cookie.SecurePolicy; set => Cookie.SecurePolicy = value; } - #endregion } } diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj index b188a58e08..093f60a3de 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/Microsoft.AspNetCore.Authentication.Cookies.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to use cookie based authentication. - netstandard2.0 + netcoreapp3.0 $(DefineConstants);SECURITY $(NoWarn);CS1591 true diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json new file mode 100644 index 0000000000..7a1ab79625 --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Cookies/breakingchanges.netcore.json @@ -0,0 +1,52 @@ + [ + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public Microsoft.AspNetCore.Http.CookieSecurePolicy get_CookieSecure()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Boolean get_CookieHttpOnly()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.String get_CookieDomain()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.String get_CookieName()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.String get_CookiePath()", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Void set_CookieDomain(System.String value)", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Void set_CookieHttpOnly(System.Boolean value)", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Void set_CookieName(System.String value)", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Void set_CookiePath(System.String value)", + "Kind": "Removal" + }, + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions", + "MemberId": "public System.Void set_CookieSecure(Microsoft.AspNetCore.Http.CookieSecurePolicy value)", + "Kind": "Removal" + } + ] \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj index 62aee1367f..f57fe26e0c 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Facebook/Microsoft.AspNetCore.Authentication.Facebook.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support Facebook's OAuth 2.0 authentication workflow. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj index de8867f91a..eba5b98adf 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Google/Microsoft.AspNetCore.Authentication.Google.csproj @@ -2,7 +2,7 @@ ASP.NET Core contains middleware to support Google's OpenId and OAuth 2.0 authentication workflows. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index 452d9639f4..4f03ccd69a 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer { private OpenIdConnectConfiguration _configuration; - public JwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IDataProtectionProvider dataProtection, ISystemClock clock) + public JwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj index e5bae5a3da..8a26c58f34 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/Microsoft.AspNetCore.Authentication.JwtBearer.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to receive an OpenID Connect bearer token. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json new file mode 100644 index 0000000000..d17003a80a --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.JwtBearer/breakingchanges.netcore.json @@ -0,0 +1,7 @@ + [ + { + "TypeId": "public class Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler : Microsoft.AspNetCore.Authentication.AuthenticationHandler", + "MemberId": "public .ctor(Microsoft.Extensions.Options.IOptionsMonitor options, Microsoft.Extensions.Logging.ILoggerFactory logger, System.Text.Encodings.Web.UrlEncoder encoder, Microsoft.AspNetCore.DataProtection.IDataProtectionProvider dataProtection, Microsoft.AspNetCore.Authentication.ISystemClock clock)", + "Kind": "Removal" + } + ] \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj index 0eddc6f764..1841157c94 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.MicrosoftAccount/Microsoft.AspNetCore.Authentication.MicrosoftAccount.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support the Microsoft Account authentication workflow. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj index 5c8a5e3a96..97d4a97036 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/Microsoft.AspNetCore.Authentication.OAuth.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support any standard OAuth 2.0 authentication workflow. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs index 808e0f9039..05ba4df68d 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthHandler.cs @@ -63,6 +63,16 @@ namespace Microsoft.AspNetCore.Authentication.OAuth var error = query["error"]; if (!StringValues.IsNullOrEmpty(error)) { + // Note: access_denied errors are special protocol errors indicating the user didn't + // approve the authorization demand requested by the remote authorization server. + // Since it's a frequent scenario (that is not caused by incorrect configuration), + // denied errors are handled differently using HandleAccessDeniedErrorAsync(). + // Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information. + if (StringValues.Equals(error, "access_denied")) + { + return await HandleAccessDeniedErrorAsync(properties); + } + var failureMessage = new StringBuilder(); failureMessage.Append(error); var errorDescription = query["error_description"]; @@ -194,7 +204,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth { if (string.IsNullOrEmpty(properties.RedirectUri)) { - properties.RedirectUri = CurrentUri; + properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } // OAuth2 10.12 CSRF diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs index 3c71f055f5..4eacbd0b30 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.OAuth/OAuthOptions.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Microsoft.AspNetCore.Http.Authentication; using System.Globalization; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; +using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.OAuth { diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj index b7f4c1704a..65ca6b8429 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Microsoft.AspNetCore.Authentication.OpenIdConnect.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support the OpenID Connect authentication workflow. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 029cf541b7..8b0b72428a 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect properties.RedirectUri = BuildRedirectUriIfRelative(Options.SignedOutRedirectUri); if (string.IsNullOrWhiteSpace(properties.RedirectUri)) { - properties.RedirectUri = CurrentUri; + properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } } Logger.PostSignOutRedirect(properties.RedirectUri); @@ -312,7 +312,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect // 2. CurrentUri if RedirectUri is not set) if (string.IsNullOrEmpty(properties.RedirectUri)) { - properties.RedirectUri = CurrentUri; + properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } Logger.PostAuthenticationLocalRedirect(properties.RedirectUri); @@ -520,6 +520,16 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect // if any of the error fields are set, throw error null if (!string.IsNullOrEmpty(authorizationResponse.Error)) { + // Note: access_denied errors are special protocol errors indicating the user didn't + // approve the authorization demand requested by the remote authorization server. + // Since it's a frequent scenario (that is not caused by incorrect configuration), + // denied errors are handled differently using HandleAccessDeniedErrorAsync(). + // Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information. + if (string.Equals(authorizationResponse.Error, "access_denied", StringComparison.Ordinal)) + { + return await HandleAccessDeniedErrorAsync(properties); + } + return HandleRequestResult.Fail(CreateOpenIdConnectProtocolException(authorizationResponse, response: null), properties); } diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj index f720d08f04..4f2aae6168 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/Microsoft.AspNetCore.Authentication.Twitter.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support Twitter's OAuth 1.0 authentication workflow. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs index 51dabbd99c..cc8591ed32 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs @@ -55,12 +55,14 @@ namespace Microsoft.AspNetCore.Authentication.Twitter var properties = requestToken.Properties; - // REVIEW: see which of these are really errors - var denied = query["denied"]; if (!StringValues.IsNullOrEmpty(denied)) { - return HandleRequestResult.Fail("The user denied permissions.", properties); + // Note: denied errors are special protocol errors indicating the user didn't + // approve the authorization demand requested by the remote authorization server. + // Since it's a frequent scenario (that is not caused by incorrect configuration), + // denied errors are handled differently using HandleAccessDeniedErrorAsync(). + return await HandleAccessDeniedErrorAsync(properties); } var returnedToken = query["oauth_token"]; @@ -130,7 +132,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter { if (string.IsNullOrEmpty(properties.RedirectUri)) { - properties.RedirectUri = CurrentUri; + properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } // If CallbackConfirmed is false, this will throw diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs index 03396807ee..269516ae1a 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterOptions.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Security.Claims; using System.Globalization; +using System.Security.Claims; using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Microsoft.AspNetCore.Http; diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj index 4edb55cb35..ae4d52aff4 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/Microsoft.AspNetCore.Authentication.WsFederation.csproj @@ -2,7 +2,7 @@ ASP.NET Core middleware that enables an application to support the WsFederation authentication workflow. - netstandard2.0 + netcoreapp3.0 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs index e47f8431f9..c4bb8fff74 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication.WsFederation/WsFederationHandler.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Authentication.WsFederation // Save the original challenge URI so we can redirect back to it when we're done. if (string.IsNullOrEmpty(properties.RedirectUri)) { - properties.RedirectUri = CurrentUri; + properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString; } var wsFederationMessage = new WsFederationMessage() diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs index b274eaace4..6c381b84ab 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/AuthenticationServiceCollectionExtensions.cs @@ -46,48 +46,6 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } - [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")] - public static IServiceCollection AddScheme(this IServiceCollection services, string authenticationScheme, string displayName, Action configureScheme, Action configureOptions) - where TOptions : AuthenticationSchemeOptions, new() - where THandler : AuthenticationHandler - { - services.AddAuthentication(o => - { - o.AddScheme(authenticationScheme, scheme => { - scheme.HandlerType = typeof(THandler); - scheme.DisplayName = displayName; - configureScheme?.Invoke(scheme); - }); - }); - if (configureOptions != null) - { - services.Configure(authenticationScheme, configureOptions); - } - services.AddTransient(); - return services; - } - - [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")] - public static IServiceCollection AddScheme(this IServiceCollection services, string authenticationScheme, Action configureOptions) - where TOptions : AuthenticationSchemeOptions, new() - where THandler : AuthenticationHandler - => services.AddScheme(authenticationScheme, displayName: null, configureScheme: null, configureOptions: configureOptions); - - [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")] - public static IServiceCollection AddScheme(this IServiceCollection services, string authenticationScheme, string displayName, Action configureOptions) - where TOptions : AuthenticationSchemeOptions, new() - where THandler : AuthenticationHandler - => services.AddScheme(authenticationScheme, displayName, configureScheme: null, configureOptions: configureOptions); - - [Obsolete("AddScheme is obsolete. Use AddAuthentication().AddScheme instead.")] - public static IServiceCollection AddRemoteScheme(this IServiceCollection services, string authenticationScheme, string displayName, Action configureOptions) - where TOptions : RemoteAuthenticationOptions, new() - where THandler : RemoteAuthenticationHandler - { - services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureSignInScheme>()); - return services.AddScheme(authenticationScheme, displayName, configureScheme: null, configureOptions: configureOptions); - } - // Used to ensure that there's always a sign in scheme private class EnsureSignInScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions { diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/Events/AccessDeniedContext.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/Events/AccessDeniedContext.cs new file mode 100644 index 0000000000..f01d69453b --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/Events/AccessDeniedContext.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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication +{ + /// + /// Provides access denied failure context information to handler providers. + /// + public class AccessDeniedContext : HandleRequestContext + { + public AccessDeniedContext( + HttpContext context, + AuthenticationScheme scheme, + RemoteAuthenticationOptions options) + : base(context, scheme, options) + { + } + + /// + /// Gets or sets the endpoint path the user agent will be redirected to. + /// By default, this property is set to . + /// + public PathString AccessDeniedPath { get; set; } + + /// + /// Additional state values for the authentication session. + /// + public AuthenticationProperties Properties { get; set; } + + /// + /// Gets or sets the return URL that will be flowed up to the access denied page. + /// If is not set, this property is not used. + /// + public string ReturnUrl { get; set; } + + /// + /// Gets or sets the parameter name that will be used to flow the return URL. + /// By default, this property is set to . + /// + public string ReturnUrlParameter { get; set; } + } +} diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs index ca0f4a5c01..be4c6ab49e 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/Events/RemoteAuthenticationEvents.cs @@ -8,12 +8,18 @@ namespace Microsoft.AspNetCore.Authentication { public class RemoteAuthenticationEvents { + public Func OnAccessDenied { get; set; } = context => Task.CompletedTask; public Func OnRemoteFailure { get; set; } = context => Task.CompletedTask; public Func OnTicketReceived { get; set; } = context => Task.CompletedTask; /// - /// Invoked when there is a remote failure + /// Invoked when an access denied error was returned by the remote server. + /// + public virtual Task AccessDenied(AccessDeniedContext context) => OnAccessDenied(context); + + /// + /// Invoked when there is a remote failure. /// public virtual Task RemoteFailure(RemoteFailureContext context) => OnRemoteFailure(context); diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs index 8cba6c0d5e..042bfabca3 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/LoggingExtensions.cs @@ -7,17 +7,20 @@ namespace Microsoft.Extensions.Logging { internal static class LoggingExtensions { - private static Action _authSchemeAuthenticated; - private static Action _authSchemeNotAuthenticated; - private static Action _authSchemeNotAuthenticatedWithFailure; - private static Action _authSchemeChallenged; - private static Action _authSchemeForbidden; - private static Action _remoteAuthenticationError; - private static Action _signInHandled; - private static Action _signInSkipped; - private static Action _correlationPropertyNotFound; - private static Action _correlationCookieNotFound; - private static Action _unexpectedCorrelationCookieValue; + private static readonly Action _authSchemeAuthenticated; + private static readonly Action _authSchemeNotAuthenticated; + private static readonly Action _authSchemeNotAuthenticatedWithFailure; + private static readonly Action _authSchemeChallenged; + private static readonly Action _authSchemeForbidden; + private static readonly Action _remoteAuthenticationError; + private static readonly Action _signInHandled; + private static readonly Action _signInSkipped; + private static readonly Action _correlationPropertyNotFound; + private static readonly Action _correlationCookieNotFound; + private static readonly Action _unexpectedCorrelationCookieValue; + private static readonly Action _accessDeniedError; + private static readonly Action _accessDeniedContextHandled; + private static readonly Action _accessDeniedContextSkipped; static LoggingExtensions() { @@ -65,6 +68,18 @@ namespace Microsoft.Extensions.Logging eventId: 16, logLevel: LogLevel.Warning, formatString: "The correlation cookie value '{CorrelationCookieName}' did not match the expected value '{CorrelationCookieValue}'."); + _accessDeniedError = LoggerMessage.Define( + eventId: 17, + logLevel: LogLevel.Information, + formatString: "Access was denied by the resource owner or by the remote server."); + _accessDeniedContextHandled = LoggerMessage.Define( + eventId: 18, + logLevel: LogLevel.Debug, + formatString: "The AccessDenied event returned Handled."); + _accessDeniedContextSkipped = LoggerMessage.Define( + eventId: 19, + logLevel: LogLevel.Debug, + formatString: "The AccessDenied event returned Skipped."); } public static void AuthenticationSchemeAuthenticated(this ILogger logger, string authenticationScheme) @@ -121,5 +136,20 @@ namespace Microsoft.Extensions.Logging { _unexpectedCorrelationCookieValue(logger, cookieName, cookieValue, null); } + + public static void AccessDeniedError(this ILogger logger) + { + _accessDeniedError(logger, null); + } + + public static void AccessDeniedContextHandled(this ILogger logger) + { + _accessDeniedContextHandled(logger, null); + } + + public static void AccessDeniedContextSkipped(this ILogger logger) + { + _accessDeniedContextSkipped(logger, null); + } } } diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj index 7e3ce4eb39..9ae0a7bccc 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj @@ -2,7 +2,7 @@ ASP.NET Core common types used by the various authentication middleware components. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authentication;security diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs index bea4895d62..c72583b5ce 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationHandler.cs @@ -5,6 +5,7 @@ using System; using System.Security.Cryptography; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -241,5 +242,48 @@ namespace Microsoft.AspNetCore.Authentication return true; } + + protected virtual async Task HandleAccessDeniedErrorAsync(AuthenticationProperties properties) + { + Logger.AccessDeniedError(); + var context = new AccessDeniedContext(Context, Scheme, Options) + { + AccessDeniedPath = Options.AccessDeniedPath, + Properties = properties, + ReturnUrl = properties?.RedirectUri, + ReturnUrlParameter = Options.ReturnUrlParameter + }; + await Events.AccessDenied(context); + + if (context.Result != null) + { + if (context.Result.Handled) + { + Logger.AccessDeniedContextHandled(); + } + else if (context.Result.Skipped) + { + Logger.AccessDeniedContextSkipped(); + } + + return context.Result; + } + + // If an access denied endpoint was specified, redirect the user agent. + // Otherwise, invoke the RemoteFailure event for further processing. + if (context.AccessDeniedPath.HasValue) + { + string uri = context.AccessDeniedPath; + if (!string.IsNullOrEmpty(context.ReturnUrlParameter) && !string.IsNullOrEmpty(context.ReturnUrl)) + { + uri = QueryHelpers.AddQueryString(uri, context.ReturnUrlParameter, context.ReturnUrl); + } + Response.Redirect(uri); + + return HandleRequestResult.Handle(); + } + + return HandleRequestResult.Fail("Access was denied by the resource owner or by the remote server.", properties); + } } } \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs b/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs index 1bd3b210e5..188b7a9917 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/RemoteAuthenticationOptions.cs @@ -89,6 +89,22 @@ namespace Microsoft.AspNetCore.Authentication /// public PathString CallbackPath { get; set; } + /// + /// Gets or sets the optional path the user agent is redirected to if the user + /// doesn't approve the authorization demand requested by the remote server. + /// This property is not set by default. In this case, an exception is thrown + /// if an access_denied response is returned by the remote authorization server. + /// + public PathString AccessDeniedPath { get; set; } + + /// + /// Gets or sets the name of the parameter used to convey the original location + /// of the user before the remote challenge was triggered up to the access denied page. + /// This property is only used when the is explicitly specified. + /// + // Note: this deliberately matches the default parameter name used by the cookie handler. + public string ReturnUrlParameter { get; set; } = "ReturnUrl"; + /// /// Gets or sets the authentication scheme corresponding to the middleware /// responsible of persisting user's identity after a successful authentication. diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json b/src/Security/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json new file mode 100644 index 0000000000..d5bc6954ef --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/breakingchanges.netcore.json @@ -0,0 +1,22 @@ + [ + { + "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", + "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddRemoteScheme(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action configureOptions) where T0 : Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions, new() where T1 : Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler", + "Kind": "Removal" + }, + { + "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", + "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.Action configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler", + "Kind": "Removal" + }, + { + "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", + "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action configureScheme, System.Action configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler", + "Kind": "Removal" + }, + { + "TypeId": "public static class Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions", + "MemberId": "public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScheme(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.String authenticationScheme, System.String displayName, System.Action configureOptions) where T0 : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, new() where T1 : Microsoft.AspNetCore.Authentication.AuthenticationHandler", + "Kind": "Removal" + } + ] \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationAppBuilderExtensions.cs b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationAppBuilderExtensions.cs new file mode 100644 index 0000000000..66f0eb6f1e --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationAppBuilderExtensions.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Authorization; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Extension methods to add authorization capabilities to an HTTP application pipeline. + /// + public static class AuthorizationAppBuilderExtensions + { + /// + /// Adds the to the specified , which enables authorization capabilities. + /// + /// The to add the middleware to. + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMiddleware(); + } + } +} diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationEndpointConventionBuilderExtensions.cs b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationEndpointConventionBuilderExtensions.cs new file mode 100644 index 0000000000..81b05d70ad --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationEndpointConventionBuilderExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Builder +{ + public static class AuthorizationEndpointConventionBuilderExtensions + { + public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params IAuthorizeData[] authorizeData) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (authorizeData == null) + { + throw new ArgumentNullException(nameof(authorizeData)); + } + + builder.Apply(endpointBuilder => + { + foreach (var data in authorizeData) + { + endpointBuilder.Metadata.Add(data); + } + }); + return builder; + } + + public static IEndpointConventionBuilder RequireAuthorization(this IEndpointConventionBuilder builder, params string[] policyNames) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (policyNames == null) + { + throw new ArgumentNullException(nameof(policyNames)); + } + + return builder.RequireAuthorization(policyNames.Select(n => new AuthorizeAttribute(n)).ToArray()); + } + } +} \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationMiddleware.cs b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationMiddleware.cs new file mode 100644 index 0000000000..1da5b123fe --- /dev/null +++ b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/AuthorizationMiddleware.cs @@ -0,0 +1,104 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Authorization +{ + public class AuthorizationMiddleware + { + private readonly RequestDelegate _next; + private readonly IAuthorizationPolicyProvider _policyProvider; + + public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) + { + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + if (policyProvider == null) + { + throw new ArgumentNullException(nameof(policyProvider)); + } + + _next = next; + _policyProvider = policyProvider; + } + + public async Task Invoke(HttpContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var endpoint = context.GetEndpoint(); + var authorizeData = endpoint?.Metadata.GetOrderedMetadata() ?? Array.Empty(); + var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData); + if (policy == null) + { + await _next(context); + return; + } + + // Policy evaluator has transient lifetime so it fetched from request services instead of injecting in constructor + var policyEvaluator = context.RequestServices.GetRequiredService(); + + var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context); + + // Allow Anonymous skips all authorization + if (endpoint?.Metadata.GetMetadata() != null) + { + await _next(context); + return; + } + + // Note that the resource will be null if there is no matched endpoint + var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint); + + if (authorizeResult.Challenged) + { + if (policy.AuthenticationSchemes.Any()) + { + foreach (var scheme in policy.AuthenticationSchemes) + { + await context.ChallengeAsync(scheme); + } + } + else + { + await context.ChallengeAsync(); + } + + return; + } + else if (authorizeResult.Forbidden) + { + if (policy.AuthenticationSchemes.Any()) + { + foreach (var scheme in policy.AuthenticationSchemes) + { + await context.ForbidAsync(scheme); + } + } + else + { + await context.ForbidAsync(); + } + + return; + } + + await _next(context); + } + } +} \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj index 16e4aa2622..d3ad99a511 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj @@ -2,8 +2,8 @@ ASP.NET Core authorization policy helper classes. - netstandard2.0 - $(NoWarn);CS1591 + netcoreapp3.0 + $(NoWarn);CS1591;NU1605 true aspnetcore;authorization @@ -14,6 +14,8 @@ + + diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs index 6899913afb..f0c5527d2d 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationOptions.cs @@ -20,13 +20,25 @@ namespace Microsoft.AspNetCore.Authorization public bool InvokeHandlersAfterFailure { get; set; } = true; /// - /// Gets or sets the default authorization policy. + /// Gets or sets the default authorization policy. Defaults to require authenticated users. /// /// - /// The default policy is to require any authenticated user. + /// The default policy used when evaluating with no policy name specified. /// public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + /// + /// Gets or sets the required authorization policy. Defaults to null. + /// + /// + /// By default the required policy is null. + /// + /// If a required policy has been specified then it is always evaluated, even if there are no + /// instances for a resource. If a resource has + /// then they are evaluated together with the required policy. + /// + public AuthorizationPolicy RequiredPolicy { get; set; } + /// /// Add an authorization policy with the provided name. /// diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs index 36e0ca7c38..b3b54f3003 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicy.cs @@ -120,46 +120,74 @@ namespace Microsoft.AspNetCore.Authorization throw new ArgumentNullException(nameof(authorizeData)); } - var policyBuilder = new AuthorizationPolicyBuilder(); - var any = false; - foreach (var authorizeDatum in authorizeData) + // Avoid allocating enumerator if the data is known to be empty + var skipEnumeratingData = false; + if (authorizeData is IList dataList) { - any = true; - var useDefaultPolicy = true; - if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)) + skipEnumeratingData = dataList.Count == 0; + } + + AuthorizationPolicyBuilder policyBuilder = null; + if (!skipEnumeratingData) + { + foreach (var authorizeDatum in authorizeData) { - var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy); - if (policy == null) + if (policyBuilder == null) { - throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy)); + policyBuilder = new AuthorizationPolicyBuilder(); } - policyBuilder.Combine(policy); - useDefaultPolicy = false; - } - var rolesSplit = authorizeDatum.Roles?.Split(','); - if (rolesSplit != null && rolesSplit.Any()) - { - var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()); - policyBuilder.RequireRole(trimmedRolesSplit); - useDefaultPolicy = false; - } - var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(','); - if (authTypesSplit != null && authTypesSplit.Any()) - { - foreach (var authType in authTypesSplit) + + var useDefaultPolicy = true; + if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)) { - if (!string.IsNullOrWhiteSpace(authType)) + var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy); + if (policy == null) { - policyBuilder.AuthenticationSchemes.Add(authType.Trim()); + throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy)); + } + policyBuilder.Combine(policy); + useDefaultPolicy = false; + } + + var rolesSplit = authorizeDatum.Roles?.Split(','); + if (rolesSplit != null && rolesSplit.Any()) + { + var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()); + policyBuilder.RequireRole(trimmedRolesSplit); + useDefaultPolicy = false; + } + + var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(','); + if (authTypesSplit != null && authTypesSplit.Any()) + { + foreach (var authType in authTypesSplit) + { + if (!string.IsNullOrWhiteSpace(authType)) + { + policyBuilder.AuthenticationSchemes.Add(authType.Trim()); + } } } - } - if (useDefaultPolicy) - { - policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync()); + + if (useDefaultPolicy) + { + policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync()); + } } } - return any ? policyBuilder.Build() : null; + + var requiredPolicy = await policyProvider.GetRequiredPolicyAsync(); + if (requiredPolicy != null) + { + if (policyBuilder == null) + { + policyBuilder = new AuthorizationPolicyBuilder(); + } + + policyBuilder.Combine(requiredPolicy); + } + + return policyBuilder?.Build(); } } } \ No newline at end of file diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs index 37335df8f2..2f483e37ce 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizationPolicyBuilder.cs @@ -96,16 +96,16 @@ namespace Microsoft.AspNetCore.Authorization /// to the current instance. /// /// The claim type required. - /// Values the claim must process one or more of for evaluation to succeed. + /// Values the claim must process one or more of for evaluation to succeed. /// A reference to this instance after the operation has completed. - public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] requiredValues) + public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues) { if (claimType == null) { throw new ArgumentNullException(nameof(claimType)); } - return RequireClaim(claimType, (IEnumerable)requiredValues); + return RequireClaim(claimType, (IEnumerable)allowedValues); } /// @@ -113,16 +113,16 @@ namespace Microsoft.AspNetCore.Authorization /// to the current instance. /// /// The claim type required. - /// Values the claim must process one or more of for evaluation to succeed. + /// Values the claim must process one or more of for evaluation to succeed. /// A reference to this instance after the operation has completed. - public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable requiredValues) + public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable allowedValues) { if (claimType == null) { throw new ArgumentNullException(nameof(claimType)); } - Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues)); + Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues)); return this; } @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Authorization /// Adds a /// to the current instance. /// - /// The roles required. + /// The allowed roles. /// A reference to this instance after the operation has completed. public AuthorizationPolicyBuilder RequireRole(params string[] roles) { @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Authorization /// Adds a /// to the current instance. /// - /// The roles required. + /// The allowed roles. /// A reference to this instance after the operation has completed. public AuthorizationPolicyBuilder RequireRole(IEnumerable roles) { @@ -247,4 +247,4 @@ namespace Microsoft.AspNetCore.Authorization return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct()); } } -} \ No newline at end of file +} diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs index 63bfa30d45..b1f0f8bae0 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/AuthorizeAttribute.cs @@ -39,15 +39,5 @@ namespace Microsoft.AspNetCore.Authorization /// Gets or sets a comma delimited list of schemes from which user information is constructed. /// public string AuthenticationSchemes { get; set; } - - /// - /// Gets or sets a comma delimited list of schemes from which user information is constructed. - /// - [Obsolete("Use AuthenticationSchemes instead.", error: false)] - public string ActiveAuthenticationSchemes - { - get => AuthenticationSchemes; - set => AuthenticationSchemes = value; - } } } diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs index 0e4329dcc0..f5e6652739 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/DefaultAuthorizationPolicyProvider.cs @@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Authorization public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider { private readonly AuthorizationOptions _options; + private Task _cachedDefaultPolicy; + private Task _cachedRequiredPolicy; /// /// Creates a new instance of . @@ -35,7 +37,26 @@ namespace Microsoft.AspNetCore.Authorization /// The default authorization policy. public Task GetDefaultPolicyAsync() { - return Task.FromResult(_options.DefaultPolicy); + return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy); + } + + /// + /// Gets the required authorization policy. + /// + /// The required authorization policy. + public Task GetRequiredPolicyAsync() + { + return GetCachedPolicy(ref _cachedRequiredPolicy, _options.RequiredPolicy); + } + + private Task GetCachedPolicy(ref Task cachedPolicy, AuthorizationPolicy currentPolicy) + { + var local = cachedPolicy; + if (local == null || local.Result != currentPolicy) + { + cachedPolicy = local = Task.FromResult(currentPolicy); + } + return local; } /// diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs b/src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs index 9e9d0f468a..4560d11e09 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/IAuthorizationPolicyProvider.cs @@ -22,5 +22,11 @@ namespace Microsoft.AspNetCore.Authorization /// /// The default authorization policy. Task GetDefaultPolicyAsync(); + + /// + /// Gets the required authorization policy. + /// + /// The required authorization policy. + Task GetRequiredPolicyAsync(); } } diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj b/src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj index ac4aa6c320..9192ac8cfa 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authorization/Microsoft.AspNetCore.Authorization.csproj @@ -5,7 +5,7 @@ Commonly used types: Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute Microsoft.AspNetCore.Authorization.AuthorizeAttribute - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore;authorization diff --git a/src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj b/src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj index 40f97633ae..81760d50ab 100644 --- a/src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj +++ b/src/Security/src/Microsoft.AspNetCore.CookiePolicy/Microsoft.AspNetCore.CookiePolicy.csproj @@ -2,7 +2,7 @@ ASP.NET Core cookie policy classes to control the behavior of cookies. - netstandard2.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore diff --git a/src/Security/test/Directory.Build.props b/src/Security/test/Directory.Build.props index e3d2d9f143..c42d54465f 100644 --- a/src/Security/test/Directory.Build.props +++ b/src/Security/test/Directory.Build.props @@ -1,13 +1,6 @@ - - netcoreapp2.2 - $(DeveloperBuildTestTfms) - $(StandardTestTfms) - $(StandardTestTfms);net461 - - diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs index 766d1e2e53..107fc5db1e 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/CookieTests.cs @@ -10,7 +10,6 @@ using System.Security.Principal; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -22,431 +21,16 @@ using Xunit; namespace Microsoft.AspNetCore.Authentication.Cookies { - public class CookieTests + public class CookieTests : SharedAuthenticationTests { private TestClock _clock = new TestClock(); - [Fact] - public async Task CanForwardDefault() + protected override string DefaultScheme => CookieAuthenticationDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(CookieAuthenticationHandler); + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddCookie(o => o.ForwardDefault = "auth1"); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await context.SignOutAsync(); - Assert.Equal(1, forwardDefault.SignOutCount); - - await context.SignInAsync(new ClaimsPrincipal()); - Assert.Equal(1, forwardDefault.SignInCount); - } - - [Fact] - public async Task ForwardSignInWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardSignIn = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.SignInAsync(new ClaimsPrincipal()); - Assert.Equal(1, specific.SignInCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignOutCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSignOutWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.SignOutAsync(); - Assert.Equal(1, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await context.SignOutAsync(); - Assert.Equal(1, selector.SignOutCount); - - await context.SignInAsync(new ClaimsPrincipal()); - Assert.Equal(1, selector.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await context.SignOutAsync(); - Assert.Equal(1, forwardDefault.SignOutCount); - - await context.SignInAsync(new ClaimsPrincipal()); - Assert.Equal(1, forwardDefault.SignInCount); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddCookie(o => - { - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await context.SignOutAsync(); - Assert.Equal(1, specific.SignOutCount); - - await context.SignInAsync(new ClaimsPrincipal()); - Assert.Equal(1, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddCookie(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(CookieAuthenticationDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("CookieAuthenticationHandler", scheme.HandlerType.Name); - Assert.Null(scheme.DisplayName); + services.AddCookie(configure); } [Fact] diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs index 0a5440e405..964af2ce72 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/FacebookTests.cs @@ -1,17 +1,8 @@ // 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.Net.Http; -using System.Security.Claims; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -20,478 +11,40 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Authentication.Facebook { - public class FacebookTests + public class FacebookTests : RemoteAuthenticationTests { - private void ConfigureDefaults(FacebookOptions o) + protected override string DefaultScheme => FacebookDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(FacebookHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddFacebook(o => + { + ConfigureDefaults(o); + configure.Invoke(o); + }); + } + + protected override void ConfigureDefaults(FacebookOptions o) { o.AppId = "whatever"; o.AppSecret = "whatever"; o.SignInScheme = "auth1"; } - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = FacebookDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddFacebook(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelf() - { - var server = CreateServer( - app => { }, - services => services.AddAuthentication().AddFacebook(o => - { - o.AppId = "whatever"; - o.AppSecret = "whatever"; - o.SignInScheme = FacebookDefaults.AuthenticationScheme; - }), - async context => - { - await context.ChallengeAsync("Facebook"); - return true; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultScheme() - { - var server = CreateServer( - app => { }, - services => services.AddAuthentication(o => o.DefaultScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o => - { - o.AppId = "whatever"; - o.AppSecret = "whatever"; - }), - async context => - { - await context.ChallengeAsync("Facebook"); - return true; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultSignInScheme() - { - var server = CreateServer( - app => { }, - services => services.AddAuthentication(o => o.DefaultSignInScheme = FacebookDefaults.AuthenticationScheme).AddFacebook(o => - { - o.AppId = "whatever"; - o.AppSecret = "whatever"; - }), - async context => - { - await context.ChallengeAsync("Facebook"); - return true; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddFacebook(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(FacebookDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("FacebookHandler", scheme.HandlerType.Name); - Assert.Equal(FacebookDefaults.AuthenticationScheme, scheme.DisplayName); - } - [Fact] public async Task ThrowsIfAppIdMissing() { diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs index 511a658ff4..ce158eaf20 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/GoogleTests.cs @@ -1,16 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. 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.Security.Claims; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -20,433 +10,42 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Authentication.Google { - public class GoogleTests + public class GoogleTests : RemoteAuthenticationTests { - private void ConfigureDefaults(GoogleOptions o) + protected override string DefaultScheme => GoogleDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(GoogleHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddGoogle(o => + { + ConfigureDefaults(o); + configure.Invoke(o); + }); + } + + protected override void ConfigureDefaults(GoogleOptions o) { o.ClientId = "whatever"; o.ClientSecret = "whatever"; o.SignInScheme = "auth1"; } - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = GoogleDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddGoogle(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelf() - { - var server = CreateServer(o => - { - o.ClientId = "Test Id"; - o.ClientSecret = "Test Secret"; - o.SignInScheme = GoogleDefaults.AuthenticationScheme; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddGoogle(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(GoogleDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("GoogleHandler", scheme.HandlerType.Name); - Assert.Equal(GoogleDefaults.AuthenticationScheme, scheme.DisplayName); - } - [Fact] public async Task ChallengeWillTriggerRedirection() { @@ -758,6 +357,70 @@ namespace Microsoft.AspNetCore.Authentication.Google Assert.Equal("The oauth state was missing or invalid.", error.GetBaseException().Message); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ReplyPathWithAccessDeniedErrorFails(bool redirect) + { + var server = CreateServer(o => + { + o.ClientId = "Test Id"; + o.ClientSecret = "Test Secret"; + o.StateDataFormat = new TestStateDataFormat(); + o.Events = redirect ? new OAuthEvents() + { + OnAccessDenied = ctx => + { + ctx.Response.Redirect("/error?FailureMessage=AccessDenied"); + ctx.HandleResponse(); + return Task.FromResult(0); + } + } : new OAuthEvents(); + }); + var sendTask = server.SendAsync("https://example.com/signin-google?error=access_denied&error_description=SoBad&error_uri=foobar&state=protected_state", + ".AspNetCore.Correlation.Google.correlationId=N"); + if (redirect) + { + var transaction = await sendTask; + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + Assert.Equal("/error?FailureMessage=AccessDenied", transaction.Response.Headers.GetValues("Location").First()); + } + else + { + var error = await Assert.ThrowsAnyAsync(() => sendTask); + Assert.Equal("Access was denied by the resource owner or by the remote server.", error.GetBaseException().Message); + } + } + + [Fact] + public async Task ReplyPathWithAccessDeniedError_AllowsCustomizingPath() + { + var server = CreateServer(o => + { + o.ClientId = "Test Id"; + o.ClientSecret = "Test Secret"; + o.StateDataFormat = new TestStateDataFormat(); + o.AccessDeniedPath = "/access-denied"; + o.Events = new OAuthEvents() + { + OnAccessDenied = ctx => + { + Assert.Equal("/access-denied", ctx.AccessDeniedPath.Value); + Assert.Equal("http://testhost/redirect", ctx.ReturnUrl); + Assert.Equal("ReturnUrl", ctx.ReturnUrlParameter); + ctx.AccessDeniedPath = "/custom-denied-page"; + ctx.ReturnUrl = "http://www.google.com/"; + ctx.ReturnUrlParameter = "rurl"; + return Task.FromResult(0); + } + }; + }); + var transaction = await server.SendAsync("https://example.com/signin-google?error=access_denied&error_description=SoBad&error_uri=foobar&state=protected_state", + ".AspNetCore.Correlation.Google.correlationId=N"); + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + Assert.Equal("/custom-denied-page?rurl=http%3A%2F%2Fwww.google.com%2F", transaction.Response.Headers.GetValues("Location").First()); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -779,7 +442,7 @@ namespace Microsoft.AspNetCore.Authentication.Google } : new OAuthEvents(); }); var sendTask = server.SendAsync("https://example.com/signin-google?error=OMG&error_description=SoBad&error_uri=foobar&state=protected_state", - ".AspNetCore.Correlation.Google.corrilationId=N"); + ".AspNetCore.Correlation.Google.correlationId=N"); if (redirect) { var transaction = await sendTask; @@ -1606,7 +1269,7 @@ namespace Microsoft.AspNetCore.Authentication.Google Assert.Equal("protected_state", protectedText); var properties = new AuthenticationProperties(new Dictionary() { - { ".xsrf", "corrilationId" }, + { ".xsrf", "correlationId" }, { "testkey", "testvalue" } }); properties.RedirectUri = "http://testhost/redirect"; diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs index d7fcdb4cad..ad77803cde 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/JwtBearerTests.cs @@ -1,6 +1,12 @@ // 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.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; using System.Linq; @@ -11,428 +17,30 @@ using System.Security.Claims; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -using Microsoft.AspNetCore.Authentication.Tests; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.AspNetCore.Testing.xunit; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; using Xunit; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class JwtBearerTests + public class JwtBearerTests : SharedAuthenticationTests { + protected override string DefaultScheme => JwtBearerDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(JwtBearerHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddJwtBearer(o => + { + ConfigureDefaults(o); + configure.Invoke(o); + }); + } + private void ConfigureDefaults(JwtBearerOptions o) { } - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddJwtBearer(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddJwtBearer(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("JwtBearerHandler", scheme.HandlerType.Name); - Assert.Null(scheme.DisplayName); - } - [Fact] public async Task BearerTokenValidation() { diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj index 6c8d518ffa..c7a3808682 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/Microsoft.AspNetCore.Authentication.Test.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp3.0 diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs index e2e13f270e..26a5484c83 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/MicrosoftAccountTests.cs @@ -1,14 +1,5 @@ // Copyright (c) .NET Foundation. All rights reserved. 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.Security.Claims; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Builder; @@ -16,438 +7,44 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Newtonsoft.Json; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Authentication.Tests.MicrosoftAccount { - public class MicrosoftAccountTests + public class MicrosoftAccountTests : RemoteAuthenticationTests { - private void ConfigureDefaults(MicrosoftAccountOptions o) + protected override string DefaultScheme => MicrosoftAccountDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(MicrosoftAccountHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddMicrosoftAccount(o => + { + ConfigureDefaults(o); + configure.Invoke(o); + }); + } + + protected override void ConfigureDefaults(MicrosoftAccountOptions o) { o.ClientId = "whatever"; o.ClientSecret = "whatever"; o.SignInScheme = "auth1"; } - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = MicrosoftAccountDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddMicrosoftAccount(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelf() - { - var server = CreateServer(o => - { - o.ClientId = "Test Id"; - o.ClientSecret = "Test Secret"; - o.SignInScheme = MicrosoftAccountDefaults.AuthenticationScheme; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddMicrosoftAccount(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(MicrosoftAccountDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("MicrosoftAccountHandler", scheme.HandlerType.Name); - Assert.Equal(MicrosoftAccountDefaults.AuthenticationScheme, scheme.DisplayName); - } - [Fact] public async Task ChallengeWillTriggerApplyRedirectEvent() { diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs index 4b822b611f..838798ceaf 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OAuthTests.cs @@ -1,447 +1,34 @@ // 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.Security.Claims; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Authentication.OAuth { - public class OAuthTests + public class OAuthTests : RemoteAuthenticationTests { - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); + protected override string DefaultScheme => OAuthDefaults.DisplayName; + protected override Type HandlerType => typeof(OAuthHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - }) - .AddOAuth("default", o => + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddOAuth(DefaultScheme, o => { ConfigureDefaults(o); - o.SignInScheme = "auth1"; - o.ForwardDefault = "auth1"; + configure.Invoke(o); }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.SignInScheme = "auth1"; - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.SignInScheme = "auth1"; - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.DefaultSignInScheme = "auth1"; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = "default"; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddOAuth("default", o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelf() - { - var server = CreateServer( - services => services.AddAuthentication().AddOAuth("weeblie", o => - { - o.SignInScheme = "weeblie"; - o.ClientId = "whatever"; - o.ClientSecret = "whatever"; - o.CallbackPath = "/whatever"; - o.AuthorizationEndpoint = "/whatever"; - o.TokenEndpoint = "/whatever"; - })); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddOAuth("oauth", o => { }); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync("oauth"); - Assert.NotNull(scheme); - Assert.Equal("OAuthHandler`1", scheme.HandlerType.Name); - Assert.Equal(OAuthDefaults.DisplayName, scheme.DisplayName); } [Fact] @@ -654,7 +241,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth Assert.Contains("scope=baz%20qux", res.Headers.Location.Query); } - private void ConfigureDefaults(OAuthOptions o) + protected override void ConfigureDefaults(OAuthOptions o) { o.ClientId = "Test Id"; o.ClientSecret = "secret"; @@ -664,6 +251,101 @@ namespace Microsoft.AspNetCore.Authentication.OAuth o.CallbackPath = "/oauth-callback"; } + [Fact] + public async Task HandleRequestAsync_RedirectsToAccessDeniedPathWhenExplicitlySet() + { + var server = CreateServer( + s => s.AddAuthentication().AddOAuth( + "Weblie", + opt => + { + opt.ClientId = "Test Id"; + opt.ClientSecret = "secret"; + opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + opt.AuthorizationEndpoint = "https://example.com/provider/login"; + opt.TokenEndpoint = "https://example.com/provider/token"; + opt.CallbackPath = "/oauth-callback"; + opt.AccessDeniedPath = "/access-denied"; + opt.StateDataFormat = new TestStateDataFormat(); + opt.Events.OnRemoteFailure = context => throw new InvalidOperationException("This event should not be called."); + })); + + var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state", + ".AspNetCore.Correlation.Weblie.correlationId=N"); + + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + Assert.Equal("/access-denied?ReturnUrl=http%3A%2F%2Ftesthost%2Fredirect", transaction.Response.Headers.Location.ToString()); + } + + [Fact] + public async Task HandleRequestAsync_InvokesAccessDeniedEvent() + { + var server = CreateServer( + s => s.AddAuthentication().AddOAuth( + "Weblie", + opt => + { + opt.ClientId = "Test Id"; + opt.ClientSecret = "secret"; + opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + opt.AuthorizationEndpoint = "https://example.com/provider/login"; + opt.TokenEndpoint = "https://example.com/provider/token"; + opt.CallbackPath = "/oauth-callback"; + opt.StateDataFormat = new TestStateDataFormat(); + opt.Events = new OAuthEvents() + { + OnAccessDenied = context => + { + Assert.Equal("testvalue", context.Properties.Items["testkey"]); + context.Response.StatusCode = StatusCodes.Status406NotAcceptable; + context.HandleResponse(); + return Task.CompletedTask; + } + }; + })); + + var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state", + ".AspNetCore.Correlation.Weblie.correlationId=N"); + + Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode); + Assert.Null(transaction.Response.Headers.Location); + } + + [Fact] + public async Task HandleRequestAsync_InvokesRemoteFailureEventWhenAccessDeniedPathIsNotExplicitlySet() + { + var server = CreateServer( + s => s.AddAuthentication().AddOAuth( + "Weblie", + opt => + { + opt.ClientId = "Test Id"; + opt.ClientSecret = "secret"; + opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + opt.AuthorizationEndpoint = "https://example.com/provider/login"; + opt.TokenEndpoint = "https://example.com/provider/token"; + opt.CallbackPath = "/oauth-callback"; + opt.StateDataFormat = new TestStateDataFormat(); + opt.Events = new OAuthEvents() + { + OnRemoteFailure = context => + { + Assert.Equal("Access was denied by the resource owner or by the remote server.", context.Failure.Message); + Assert.Equal("testvalue", context.Properties.Items["testkey"]); + context.Response.StatusCode = StatusCodes.Status406NotAcceptable; + context.HandleResponse(); + return Task.CompletedTask; + } + }; + })); + + var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=access_denied&state=protected_state", + ".AspNetCore.Correlation.Weblie.correlationId=N"); + + Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode); + Assert.Null(transaction.Response.Headers.Location); + } + [Fact] public async Task RemoteAuthenticationFailed_OAuthError_IncludesProperties() { @@ -683,7 +365,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth { OnRemoteFailure = context => { - Assert.Contains("declined", context.Failure.Message); + Assert.Contains("custom_error", context.Failure.Message); Assert.Equal("testvalue", context.Properties.Items["testkey"]); context.Response.StatusCode = StatusCodes.Status406NotAcceptable; context.HandleResponse(); @@ -692,8 +374,8 @@ namespace Microsoft.AspNetCore.Authentication.OAuth }; })); - var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=declined&state=protected_state", - ".AspNetCore.Correlation.Weblie.corrilationId=N"); + var transaction = await server.SendAsync("https://www.example.com/oauth-callback?error=custom_error&state=protected_state", + ".AspNetCore.Correlation.Weblie.correlationId=N"); Assert.Equal(HttpStatusCode.NotAcceptable, transaction.Response.StatusCode); Assert.Null(transaction.Response.Headers.Location); @@ -736,7 +418,7 @@ namespace Microsoft.AspNetCore.Authentication.OAuth Assert.Equal("protected_state", protectedText); var properties = new AuthenticationProperties(new Dictionary() { - { ".xsrf", "corrilationId" }, + { ".xsrf", "correlationId" }, { "testkey", "testvalue" } }); properties.RedirectUri = "http://testhost/redirect"; diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs index 7530b00c31..090bf3dec4 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectEventTests.cs @@ -783,6 +783,52 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect events.ValidateExpectations(); } + [Fact] + public async Task OnAccessDenied_Skip_NoMoreEventsRun() + { + var events = new ExpectedOidcEvents() + { + ExpectMessageReceived = true, + ExpectAccessDenied = true + }; + events.OnAccessDenied = context => + { + context.SkipHandler(); + return Task.FromResult(0); + }; + var server = CreateServer(events, AppWritePath); + + var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/signin-oidc", await response.Content.ReadAsStringAsync()); + events.ValidateExpectations(); + } + + [Fact] + public async Task OnAccessDenied_Handled_NoMoreEventsRun() + { + var events = new ExpectedOidcEvents() + { + ExpectMessageReceived = true, + ExpectAccessDenied = true + }; + events.OnAccessDenied = context => + { + Assert.Equal("testvalue", context.Properties.Items["testkey"]); + context.HandleResponse(); + context.Response.StatusCode = StatusCodes.Status202Accepted; + return Task.FromResult(0); + }; + var server = CreateServer(events, AppNotImpl); + + var response = await PostAsync(server, "signin-oidc", "error=access_denied&state=protected_state"); + + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + events.ValidateExpectations(); + } + [Fact] public async Task OnRemoteFailure_Skip_NoMoreEventsRun() { @@ -1099,6 +1145,9 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect public bool ExpectTokenValidated { get; set; } public bool InvokedTokenValidated { get; set; } + public bool ExpectAccessDenied { get; set; } + public bool InvokedAccessDenied { get; set; } + public bool ExpectRemoteFailure { get; set; } public bool InvokedRemoteFailure { get; set; } @@ -1168,6 +1217,12 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect return base.TicketReceived(context); } + public override Task AccessDenied(AccessDeniedContext context) + { + InvokedAccessDenied = true; + return base.AccessDenied(context); + } + public override Task RemoteFailure(RemoteFailureContext context) { InvokedRemoteFailure = true; @@ -1201,6 +1256,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.Equal(ExpectUserInfoReceived, InvokedUserInfoReceived); Assert.Equal(ExpectAuthenticationFailed, InvokeAuthenticationFailed); Assert.Equal(ExpectTicketReceived, InvokedTicketReceived); + Assert.Equal(ExpectAccessDenied, InvokedAccessDenied); Assert.Equal(ExpectRemoteFailure, InvokedRemoteFailure); Assert.Equal(ExpectRedirectForSignOut, InvokedRedirectForSignOut); Assert.Equal(ExpectRemoteSignOut, InvokedRemoteSignOut); @@ -1248,7 +1304,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect private Task PostAsync(TestServer server, string path, string form) { var client = server.CreateClient(); - var cookie = ".AspNetCore.Correlation." + OpenIdConnectDefaults.AuthenticationScheme + ".corrilationId=N"; + var cookie = ".AspNetCore.Correlation." + OpenIdConnectDefaults.AuthenticationScheme + ".correlationId=N"; client.DefaultRequestHeaders.Add("Cookie", cookie); return client.PostAsync("signin-oidc", new StringContent(form, Encoding.ASCII, "application/x-www-form-urlencoded")); @@ -1273,7 +1329,7 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect Assert.Equal("protected_state", protectedText); var properties = new AuthenticationProperties(new Dictionary() { - { ".xsrf", "corrilationId" }, + { ".xsrf", "correlationId" }, { OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, "redirect_uri" }, { "testkey", "testvalue" } }); diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/RemoteAuthenticationTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/RemoteAuthenticationTests.cs new file mode 100644 index 0000000000..d477e75347 --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/RemoteAuthenticationTests.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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication +{ + public abstract class RemoteAuthenticationTests : SharedAuthenticationTests where TOptions : RemoteAuthenticationOptions + { + protected override string DisplayName => DefaultScheme; + + private TestServer CreateServer(Action configureOptions, Func testpath = null, bool isDefault = true) + => CreateServerWithServices(s => + { + var builder = s.AddAuthentication(); + if (isDefault) + { + s.Configure(o => o.DefaultScheme = DefaultScheme); + } + RegisterAuth(builder, configureOptions); + s.AddSingleton(Clock); + }, testpath); + + + protected virtual TestServer CreateServerWithServices(Action configureServices, Func testpath = null) + { + //private static TestServer CreateServer(Action configure, Action configureServices, Func> handler) + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use(async (context, next) => + { + if (testpath != null) + { + await testpath(context); + } + await next(); + }); + }) + .ConfigureServices(configureServices); + return new TestServer(builder); + } + + protected abstract void ConfigureDefaults(TOptions o); + + [Fact] + public async Task VerifySignInSchemeCannotBeSetToSelf() + { + var server = CreateServer( + o => + { + ConfigureDefaults(o); + o.SignInScheme = DefaultScheme; + }, + context => context.ChallengeAsync(DefaultScheme)); + var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); + Assert.Contains("cannot be set to itself", error.Message); + } + + [Fact] + public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultScheme() + { + var server = CreateServer( + o => o.SignInScheme = null, + context => context.ChallengeAsync(DefaultScheme), + isDefault: true); + var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); + Assert.Contains("cannot be set to itself", error.Message); + } + + [Fact] + public async Task VerifySignInSchemeCannotBeSetToSelfUsingDefaultSignInScheme() + { + var server = CreateServerWithServices( + services => + { + var builder = services.AddAuthentication(o => o.DefaultSignInScheme = DefaultScheme); + RegisterAuth(builder, o => o.SignInScheme = null); + }, + context => context.ChallengeAsync(DefaultScheme)); + var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); + Assert.Contains("cannot be set to itself", error.Message); + } + } +} diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/SharedAuthenticationTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/SharedAuthenticationTests.cs new file mode 100644 index 0000000000..4590c4915d --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/SharedAuthenticationTests.cs @@ -0,0 +1,510 @@ +// 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.Tests; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Authentication +{ + public abstract class SharedAuthenticationTests where TOptions : AuthenticationSchemeOptions + { + protected TestClock Clock { get; } = new TestClock(); + + protected abstract string DefaultScheme { get; } + protected virtual string DisplayName { get; } + protected abstract Type HandlerType { get; } + + protected virtual bool SupportsSignIn { get => true; } + protected virtual bool SupportsSignOut { get => true; } + + protected abstract void RegisterAuth(AuthenticationBuilder services, Action configure); + + [Fact] + public async Task CanForwardDefault() + { + var services = new ServiceCollection().AddLogging(); + + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + }); + RegisterAuth(builder, o => o.ForwardDefault = "auth1"); + + var forwardDefault = new TestHandler(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + + await context.AuthenticateAsync(); + Assert.Equal(1, forwardDefault.AuthenticateCount); + + await context.ForbidAsync(); + Assert.Equal(1, forwardDefault.ForbidCount); + + await context.ChallengeAsync(); + Assert.Equal(1, forwardDefault.ChallengeCount); + + if (SupportsSignOut) + { + await context.SignOutAsync(); + Assert.Equal(1, forwardDefault.SignOutCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignOutAsync()); + } + + if (SupportsSignIn) + { + await context.SignInAsync(new ClaimsPrincipal()); + Assert.Equal(1, forwardDefault.SignInCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); + } + } + + [Fact] + public async Task ForwardSignInWinsOverDefault() + { + if (SupportsSignIn) + { + var services = new ServiceCollection().AddLogging(); + + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardSignIn = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.SignInAsync(new ClaimsPrincipal()); + Assert.Equal(1, specific.SignInCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignOutCount); + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + } + } + + [Fact] + public async Task ForwardSignOutWinsOverDefault() + { + if (SupportsSignOut) + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardSignOut = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.SignOutAsync(); + Assert.Equal(1, specific.SignOutCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + } + } + + [Fact] + public async Task ForwardForbidWinsOverDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardForbid = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.ForbidAsync(); + Assert.Equal(0, specific.SignOutCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(1, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + } + + [Fact] + public async Task ForwardAuthenticateWinsOverDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardAuthenticate = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.AuthenticateAsync(); + Assert.Equal(0, specific.SignOutCount); + Assert.Equal(1, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + } + + [Fact] + public async Task ForwardChallengeWinsOverDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardChallenge = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.ChallengeAsync(); + Assert.Equal(0, specific.SignOutCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(1, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + } + + [Fact] + public async Task ForwardSelectorWinsOverDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("selector", "selector"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardDefaultSelector = _ => "selector"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + var selector = new TestHandler3(); + services.AddSingleton(selector); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.AuthenticateAsync(); + Assert.Equal(1, selector.AuthenticateCount); + + await context.ForbidAsync(); + Assert.Equal(1, selector.ForbidCount); + + await context.ChallengeAsync(); + Assert.Equal(1, selector.ChallengeCount); + + if (SupportsSignOut) + { + await context.SignOutAsync(); + Assert.Equal(1, selector.SignOutCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignOutAsync()); + } + + if (SupportsSignIn) + { + await context.SignInAsync(new ClaimsPrincipal()); + Assert.Equal(1, selector.SignInCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); + } + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + Assert.Equal(0, specific.SignOutCount); + } + + [Fact] + public async Task NullForwardSelectorUsesDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("selector", "selector"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardDefaultSelector = _ => null; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + var selector = new TestHandler3(); + services.AddSingleton(selector); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.AuthenticateAsync(); + Assert.Equal(1, forwardDefault.AuthenticateCount); + + await context.ForbidAsync(); + Assert.Equal(1, forwardDefault.ForbidCount); + + await context.ChallengeAsync(); + Assert.Equal(1, forwardDefault.ChallengeCount); + + if (SupportsSignOut) + { + await context.SignOutAsync(); + Assert.Equal(1, forwardDefault.SignOutCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignOutAsync()); + } + + if (SupportsSignIn) + { + await context.SignInAsync(new ClaimsPrincipal()); + Assert.Equal(1, forwardDefault.SignInCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); + } + + Assert.Equal(0, selector.AuthenticateCount); + Assert.Equal(0, selector.ForbidCount); + Assert.Equal(0, selector.ChallengeCount); + Assert.Equal(0, selector.SignInCount); + Assert.Equal(0, selector.SignOutCount); + Assert.Equal(0, specific.AuthenticateCount); + Assert.Equal(0, specific.ForbidCount); + Assert.Equal(0, specific.ChallengeCount); + Assert.Equal(0, specific.SignInCount); + Assert.Equal(0, specific.SignOutCount); + } + + [Fact] + public async Task SpecificForwardWinsOverSelectorAndDefault() + { + var services = new ServiceCollection().AddLogging(); + var builder = services.AddAuthentication(o => + { + o.DefaultScheme = DefaultScheme; + o.AddScheme("auth1", "auth1"); + o.AddScheme("selector", "selector"); + o.AddScheme("specific", "specific"); + }); + RegisterAuth(builder, o => + { + o.ForwardDefault = "auth1"; + o.ForwardDefaultSelector = _ => "selector"; + o.ForwardAuthenticate = "specific"; + o.ForwardChallenge = "specific"; + o.ForwardSignIn = "specific"; + o.ForwardSignOut = "specific"; + o.ForwardForbid = "specific"; + }); + + var specific = new TestHandler(); + services.AddSingleton(specific); + var forwardDefault = new TestHandler2(); + services.AddSingleton(forwardDefault); + var selector = new TestHandler3(); + services.AddSingleton(selector); + + var sp = services.BuildServiceProvider(); + var context = new DefaultHttpContext(); + context.RequestServices = sp; + + await context.AuthenticateAsync(); + Assert.Equal(1, specific.AuthenticateCount); + + await context.ForbidAsync(); + Assert.Equal(1, specific.ForbidCount); + + await context.ChallengeAsync(); + Assert.Equal(1, specific.ChallengeCount); + + if (SupportsSignOut) + { + await context.SignOutAsync(); + Assert.Equal(1, specific.SignOutCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignOutAsync()); + } + + if (SupportsSignIn) + { + await context.SignInAsync(new ClaimsPrincipal()); + Assert.Equal(1, specific.SignInCount); + } + else + { + await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); + } + + Assert.Equal(0, forwardDefault.AuthenticateCount); + Assert.Equal(0, forwardDefault.ForbidCount); + Assert.Equal(0, forwardDefault.ChallengeCount); + Assert.Equal(0, forwardDefault.SignInCount); + Assert.Equal(0, forwardDefault.SignOutCount); + Assert.Equal(0, selector.AuthenticateCount); + Assert.Equal(0, selector.ForbidCount); + Assert.Equal(0, selector.ChallengeCount); + Assert.Equal(0, selector.SignInCount); + Assert.Equal(0, selector.SignOutCount); + } + + [Fact] + public async Task VerifySchemeDefaults() + { + var services = new ServiceCollection(); + var builder = services.AddAuthentication(); + RegisterAuth(builder, o => { }); + var sp = services.BuildServiceProvider(); + var schemeProvider = sp.GetRequiredService(); + var scheme = await schemeProvider.GetSchemeAsync(DefaultScheme); + Assert.NotNull(scheme); + Assert.Equal(HandlerType, scheme.HandlerType); + Assert.Equal(DisplayName, scheme.DisplayName); + } + } +} diff --git a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs index c1937d136c..8eb7a5cd7d 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs @@ -1,5 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information. +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; using System; using System.Linq; using System.Net; @@ -7,440 +13,33 @@ using System.Net.Http; using System.Security.Claims; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.Tests; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Authentication.Twitter { - public class TwitterTests + public class TwitterTests : RemoteAuthenticationTests { - private void ConfigureDefaults(TwitterOptions o) + protected override string DefaultScheme => TwitterDefaults.AuthenticationScheme; + protected override Type HandlerType => typeof(TwitterHandler); + protected override bool SupportsSignIn { get => false; } + protected override bool SupportsSignOut { get => false; } + + protected override void RegisterAuth(AuthenticationBuilder services, Action configure) + { + services.AddTwitter(o => + { + ConfigureDefaults(o); + configure.Invoke(o); + }); + } + + protected override void ConfigureDefaults(TwitterOptions o) { o.ConsumerKey = "whatever"; o.ConsumerSecret = "whatever"; o.SignInScheme = "auth1"; } - [Fact] - public async Task CanForwardDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - }); - - var forwardDefault = new TestHandler(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignInThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - } - - [Fact] - public async Task ForwardSignOutThrows() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardSignOut = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - } - - [Fact] - public async Task ForwardForbidWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ForbidAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(1, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardAuthenticateWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardAuthenticate = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(1, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardChallengeWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("specific", "specific"); - o.AddScheme("auth1", "auth1"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardChallenge = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.ChallengeAsync(); - Assert.Equal(0, specific.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(1, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - } - - [Fact] - public async Task ForwardSelectorWinsOverDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, selector.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, selector.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, selector.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task NullForwardSelectorUsesDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => null; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, forwardDefault.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, forwardDefault.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, forwardDefault.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - Assert.Equal(0, specific.AuthenticateCount); - Assert.Equal(0, specific.ForbidCount); - Assert.Equal(0, specific.ChallengeCount); - Assert.Equal(0, specific.SignInCount); - Assert.Equal(0, specific.SignOutCount); - } - - [Fact] - public async Task SpecificForwardWinsOverSelectorAndDefault() - { - var services = new ServiceCollection().AddLogging(); - services.AddAuthentication(o => - { - o.DefaultScheme = TwitterDefaults.AuthenticationScheme; - o.AddScheme("auth1", "auth1"); - o.AddScheme("selector", "selector"); - o.AddScheme("specific", "specific"); - }) - .AddTwitter(o => - { - ConfigureDefaults(o); - o.ForwardDefault = "auth1"; - o.ForwardDefaultSelector = _ => "selector"; - o.ForwardAuthenticate = "specific"; - o.ForwardChallenge = "specific"; - o.ForwardSignIn = "specific"; - o.ForwardSignOut = "specific"; - o.ForwardForbid = "specific"; - }); - - var specific = new TestHandler(); - services.AddSingleton(specific); - var forwardDefault = new TestHandler2(); - services.AddSingleton(forwardDefault); - var selector = new TestHandler3(); - services.AddSingleton(selector); - - var sp = services.BuildServiceProvider(); - var context = new DefaultHttpContext(); - context.RequestServices = sp; - - await context.AuthenticateAsync(); - Assert.Equal(1, specific.AuthenticateCount); - - await context.ForbidAsync(); - Assert.Equal(1, specific.ForbidCount); - - await context.ChallengeAsync(); - Assert.Equal(1, specific.ChallengeCount); - - await Assert.ThrowsAsync(() => context.SignOutAsync()); - await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); - - Assert.Equal(0, forwardDefault.AuthenticateCount); - Assert.Equal(0, forwardDefault.ForbidCount); - Assert.Equal(0, forwardDefault.ChallengeCount); - Assert.Equal(0, forwardDefault.SignInCount); - Assert.Equal(0, forwardDefault.SignOutCount); - Assert.Equal(0, selector.AuthenticateCount); - Assert.Equal(0, selector.ForbidCount); - Assert.Equal(0, selector.ChallengeCount); - Assert.Equal(0, selector.SignInCount); - Assert.Equal(0, selector.SignOutCount); - } - - [Fact] - public async Task VerifySignInSchemeCannotBeSetToSelf() - { - var server = CreateServer(o => - { - o.ConsumerKey = "Test Consumer Key"; - o.ConsumerSecret = "Test Consumer Secret"; - o.SignInScheme = TwitterDefaults.AuthenticationScheme; - }); - var error = await Assert.ThrowsAsync(() => server.SendAsync("https://example.com/challenge")); - Assert.Contains("cannot be set to itself", error.Message); - } - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddTwitter(); - var sp = services.BuildServiceProvider(); - var schemeProvider = sp.GetRequiredService(); - var scheme = await schemeProvider.GetSchemeAsync(TwitterDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("TwitterHandler", scheme.HandlerType.Name); - Assert.Equal(TwitterDefaults.AuthenticationScheme, scheme.DisplayName); - } - [Fact] public async Task ChallengeWillTriggerApplyRedirectEvent() { @@ -575,6 +174,94 @@ namespace Microsoft.AspNetCore.Authentication.Twitter Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location); } + [Fact] + public async Task HandleRequestAsync_RedirectsToAccessDeniedPathWhenExplicitlySet() + { + var server = CreateServer(o => + { + o.ConsumerKey = "Test Consumer Key"; + o.ConsumerSecret = "Test Consumer Secret"; + o.BackchannelHttpHandler = new TestHttpMessageHandler + { + Sender = BackchannelRequestToken + }; + o.AccessDeniedPath = "/access-denied"; + o.Events.OnRemoteFailure = context => throw new InvalidOperationException("This event should not be called."); + }, + async context => + { + var properties = new AuthenticationProperties(); + properties.Items["testkey"] = "testvalue"; + await context.ChallengeAsync("Twitter", properties); + return true; + }); + var transaction = await server.SendAsync("http://example.com/challenge"); + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + var location = transaction.Response.Headers.Location.AbsoluteUri; + Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location); + Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie)); + Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues)); + Assert.Single(setCookieValues); + var setCookieValue = setCookieValues.Single(); + var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value); + + var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG"); + request.Headers.Add(HeaderNames.Cookie, cookie.ToString()); + var client = server.CreateClient(); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal("/access-denied?ReturnUrl=%2Fchallenge", response.Headers.Location.ToString()); + } + + [Fact] + public async Task BadCallbackCallsAccessDeniedWithState() + { + var server = CreateServer(o => + { + o.ConsumerKey = "Test Consumer Key"; + o.ConsumerSecret = "Test Consumer Secret"; + o.BackchannelHttpHandler = new TestHttpMessageHandler + { + Sender = BackchannelRequestToken + }; + o.Events = new TwitterEvents() + { + OnAccessDenied = context => + { + Assert.NotNull(context.Properties); + Assert.Equal("testvalue", context.Properties.Items["testkey"]); + context.Response.StatusCode = StatusCodes.Status406NotAcceptable; + context.HandleResponse(); + return Task.CompletedTask; + } + }; + }, + async context => + { + var properties = new AuthenticationProperties(); + properties.Items["testkey"] = "testvalue"; + await context.ChallengeAsync("Twitter", properties); + return true; + }); + var transaction = await server.SendAsync("http://example.com/challenge"); + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + var location = transaction.Response.Headers.Location.AbsoluteUri; + Assert.Contains("https://api.twitter.com/oauth/authenticate?oauth_token=", location); + Assert.True(transaction.Response.Headers.TryGetValues(HeaderNames.SetCookie, out var setCookie)); + Assert.True(SetCookieHeaderValue.TryParseList(setCookie.ToList(), out var setCookieValues)); + Assert.Single(setCookieValues); + var setCookieValue = setCookieValues.Single(); + var cookie = new CookieHeaderValue(setCookieValue.Name, setCookieValue.Value); + + var request = new HttpRequestMessage(HttpMethod.Get, "/signin-twitter?denied=ABCDEFG"); + request.Headers.Add(HeaderNames.Cookie, cookie.ToString()); + var client = server.CreateClient(); + var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode); + } + [Fact] public async Task BadCallbackCallsRemoteAuthFailedWithState() { @@ -591,7 +278,7 @@ namespace Microsoft.AspNetCore.Authentication.Twitter OnRemoteFailure = context => { Assert.NotNull(context.Failure); - Assert.Equal("The user denied permissions.", context.Failure.Message); + Assert.Equal("Access was denied by the resource owner or by the remote server.", context.Failure.Message); Assert.NotNull(context.Properties); Assert.Equal("testvalue", context.Properties.Items["testkey"]); context.Response.StatusCode = StatusCodes.Status406NotAcceptable; diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationAppBuilderExtensionsTests.cs b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationAppBuilderExtensionsTests.cs new file mode 100644 index 0000000000..c6fb596f18 --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationAppBuilderExtensionsTests.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 System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization.Test.TestObjects; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder.Internal; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class AuthorizationAppBuilderExtensionsTests + { + [Fact] + public async Task UseAuthorization_RegistersMiddleware() + { + // Arrange + var authenticationService = new TestAuthenticationService(); + var services = CreateServices(authenticationService); + + var app = new ApplicationBuilder(services); + + app.UseAuthorization(); + + var appFunc = app.Build(); + + var endpoint = new Endpoint( + null, + new EndpointMetadataCollection(new AuthorizeAttribute()), + "Test endpoint"); + + var httpContext = new DefaultHttpContext(); + httpContext.RequestServices = services; + httpContext.SetEndpoint(endpoint); + + // Act + await appFunc(httpContext); + + // Assert + Assert.True(authenticationService.ChallengeCalled); + } + + private IServiceProvider CreateServices(IAuthenticationService authenticationService) + { + var services = new ServiceCollection(); + + services.AddAuthorization(options => { }); + services.AddAuthorizationPolicyEvaluator(); + services.AddLogging(); + services.AddSingleton(authenticationService); + + var serviceProvder = services.BuildServiceProvider(); + + return serviceProvder; + } + } +} diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationEndpointConventionBuilderExtensionsTests.cs b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationEndpointConventionBuilderExtensionsTests.cs new file mode 100644 index 0000000000..a2ab8fa8b5 --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationEndpointConventionBuilderExtensionsTests.cs @@ -0,0 +1,63 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class AuthorizationEndpointConventionBuilderExtensionsTests + { + [Fact] + public void RequireAuthorization_IAuthorizeData() + { + // Arrange + var builder = new TestEndpointConventionBuilder(); + var metadata = new AuthorizeAttribute(); + + // Act + builder.RequireAuthorization(metadata); + + // Assert + var convention = Assert.Single(builder.Conventions); + + var endpointModel = new RouteEndpointModel((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); + convention(endpointModel); + + Assert.Equal(metadata, Assert.Single(endpointModel.Metadata)); + } + + [Fact] + public void RequireAuthorization_PolicyName() + { + // Arrange + var builder = new TestEndpointConventionBuilder(); + + // Act + builder.RequireAuthorization("policy"); + + // Assert + var convention = Assert.Single(builder.Conventions); + + var endpointModel = new RouteEndpointModel((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); + convention(endpointModel); + + Assert.Equal("policy", Assert.IsAssignableFrom(Assert.Single(endpointModel.Metadata)).Policy); + } + + private class TestEndpointConventionBuilder : IEndpointConventionBuilder + { + public IList> Conventions { get; } = new List>(); + + public void Apply(Action convention) + { + Conventions.Add(convention); + } + } + } +} diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationMiddlewareTests.cs b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationMiddlewareTests.cs new file mode 100644 index 0000000000..655a4fbf8b --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/AuthorizationMiddlewareTests.cs @@ -0,0 +1,459 @@ +// 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.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.Endpoints; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Authorization.Test +{ + public class AuthorizationMiddlewareTests + { + [Fact] + public async Task NoEndpoint_AnonymousUser_Allows() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + } + + [Fact] + public async Task NoEndpointWithRequired_AnonymousUser_Challenges() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + } + + [Fact] + public async Task HasEndpointWithoutAuth_AnonymousUser_Allows() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint()); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + } + + [Fact] + public async Task HasEndpointWithRequiredWithoutAuth_AnonymousUser_Challenges() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint()); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + } + + [Fact] + public async Task HasEndpointWithAuth_AnonymousUser_Challenges() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.True(authenticationService.ChallengeCalled); + } + + [Fact] + public async Task HasEndpointWithAuth_AnonymousUser_ChallengePerScheme() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().AddAuthenticationSchemes("schema1", "schema2").Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.Equal(2, authenticationService.ChallengeCount); + } + + [Fact] + public async Task OnAuthorizationAsync_WillCallPolicyProvider() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); + var policyProvider = new Mock(); + var getPolicyCount = 0; + var getRequiredPolicyCount = 0; + policyProvider.Setup(p => p.GetPolicyAsync(It.IsAny())).ReturnsAsync(policy) + .Callback(() => getPolicyCount++); + policyProvider.Setup(p => p.GetRequiredPolicyAsync()).ReturnsAsync(policy) + .Callback(() => getRequiredPolicyCount++); + var next = new TestRequestDelegate(); + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute("whatever"))); + + // Act & Assert + await middleware.Invoke(context); + Assert.Equal(1, getPolicyCount); + Assert.Equal(1, getRequiredPolicyCount); + Assert.Equal(1, next.CalledCount); + + await middleware.Invoke(context); + Assert.Equal(2, getPolicyCount); + Assert.Equal(2, getRequiredPolicyCount); + Assert.Equal(2, next.CalledCount); + + await middleware.Invoke(context); + Assert.Equal(3, getPolicyCount); + Assert.Equal(3, getRequiredPolicyCount); + Assert.Equal(3, next.CalledCount); + } + + [Fact] + public async Task Invoke_ValidClaimShouldNotFail() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireClaim("Permission", "CanViewPage").Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute())); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + } + + [Fact] + public async Task HasEndpointWithAuthAndAllowAnonymous_AnonymousUser_Allows() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(anonymous: true, endpoint: CreateEndpoint(new AuthorizeAttribute(), new AllowAnonymousAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + Assert.False(authenticationService.ChallengeCalled); + } + + [Fact] + public async Task HasEndpointWithAuth_AuthenticatedUser_Allows() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + Assert.False(authenticationService.ChallengeCalled); + } + + [Fact] + public async Task Invoke_AuthSchemesFailShouldSetEmptyPrincipalOnContext() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("Fails").RequireAuthenticatedUser().Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.NotNull(context.User?.Identity); + Assert.True(authenticationService.AuthenticateCalled); + Assert.True(authenticationService.ChallengeCalled); + } + + [Fact] + public async Task Invoke_SingleValidClaimShouldSucceed() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireClaim("Permission", "CanViewComment", "CanViewPage").Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute())); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.True(next.Called); + } + + [Fact] + public async Task AuthZResourceShouldBeEndpoint() + { + // Arrange + object resource = null; + var policy = new AuthorizationPolicyBuilder().RequireAssertion(c => + { + resource = c.Resource; + return true; + }).Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var endpoint = CreateEndpoint(new AuthorizeAttribute()); + var context = GetHttpContext(endpoint: endpoint); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.Equal(endpoint, resource); + } + + [Fact] + public async Task Invoke_RequireUnknownRoleShouldForbid() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireRole("Wut").Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.False(authenticationService.ChallengeCalled); + Assert.True(authenticationService.ForbidCalled); + } + + [Fact] + public async Task Invoke_RequireUnknownRole_ForbidPerScheme() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequireRole("Wut").AddAuthenticationSchemes("Basic", "Bearer").Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.Equal(2, authenticationService.ForbidCount); + } + + [Fact] + public async Task Invoke_InvalidClaimShouldForbid() + { + // Arrange + var policy = new AuthorizationPolicyBuilder() + .RequireClaim("Permission", "CanViewComment") + .Build(); + var policyProvider = new Mock(); + policyProvider.Setup(p => p.GetDefaultPolicyAsync()).ReturnsAsync(policy); + var next = new TestRequestDelegate(); + var authenticationService = new TestAuthenticationService(); + + var middleware = CreateMiddleware(next.Invoke, policyProvider.Object); + var context = GetHttpContext(endpoint: CreateEndpoint(new AuthorizeAttribute()), authenticationService: authenticationService); + + // Act + await middleware.Invoke(context); + + // Assert + Assert.False(next.Called); + Assert.False(authenticationService.ChallengeCalled); + Assert.True(authenticationService.ForbidCalled); + } + + private AuthorizationMiddleware CreateMiddleware(RequestDelegate requestDelegate = null, IAuthorizationPolicyProvider policyProvider = null) + { + requestDelegate = requestDelegate ?? ((context) => Task.CompletedTask); + + return new AuthorizationMiddleware(requestDelegate, policyProvider); + } + + private Endpoint CreateEndpoint(params object[] metadata) + { + return new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(metadata), "Test endpoint"); + } + + private HttpContext GetHttpContext( + bool anonymous = false, + Action registerServices = null, + Endpoint endpoint = null, + IAuthenticationService authenticationService = null) + { + var basicPrincipal = new ClaimsPrincipal( + new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CanViewPage"), + new Claim(ClaimTypes.Role, "Administrator"), + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.NameIdentifier, "John")}, + "Basic")); + + var validUser = basicPrincipal; + + var bearerIdentity = new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CupBearer"), + new Claim(ClaimTypes.Role, "Token"), + new Claim(ClaimTypes.NameIdentifier, "John Bear")}, + "Bearer"); + + validUser.AddIdentity(bearerIdentity); + + // ServiceProvider + var serviceCollection = new ServiceCollection(); + + authenticationService = authenticationService ?? Mock.Of(); + + serviceCollection.AddSingleton(authenticationService); + serviceCollection.AddOptions(); + serviceCollection.AddLogging(); + serviceCollection.AddAuthorization(); + serviceCollection.AddAuthorizationPolicyEvaluator(); + registerServices?.Invoke(serviceCollection); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + //// HttpContext + var httpContext = new DefaultHttpContext(); + if (endpoint != null) + { + httpContext.SetEndpoint(endpoint); + } + httpContext.RequestServices = serviceProvider; + if (!anonymous) + { + httpContext.User = validUser; + } + + return httpContext; + } + + private class TestRequestDelegate + { + private readonly int _statusCode; + + public bool Called => CalledCount > 0; + public int CalledCount { get; private set; } + + public TestRequestDelegate(int statusCode = 200) + { + _statusCode = statusCode; + } + + public Task Invoke(HttpContext context) + { + CalledCount++; + context.Response.StatusCode = _statusCode; + return Task.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs index ef17b94620..230e95c4c4 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/DefaultAuthorizationServiceTests.cs @@ -1025,6 +1025,11 @@ namespace Microsoft.AspNetCore.Authorization.Test return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); } + public Task GetRequiredPolicyAsync() + { + return Task.FromResult(null); + } + public Task GetPolicyAsync(string policyName) { return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); @@ -1059,6 +1064,11 @@ namespace Microsoft.AspNetCore.Authorization.Test return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); } + public Task GetRequiredPolicyAsync() + { + return Task.FromResult(null); + } + public Task GetPolicyAsync(string policyName) { return Task.FromResult(new AuthorizationPolicyBuilder().RequireClaim(policyName).Build()); diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj index d4379c3aab..9ff42a070c 100644 --- a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/Microsoft.AspNetCore.Authorization.Test.csproj @@ -1,7 +1,8 @@  - $(StandardTestTfms) + netcoreapp3.0 + $(NoWarn);NU1605 @@ -11,8 +12,10 @@ + + diff --git a/src/Security/test/Microsoft.AspNetCore.Authorization.Test/TestObjects/TestAuthenticationService.cs b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/TestObjects/TestAuthenticationService.cs new file mode 100644 index 0000000000..0c17693d16 --- /dev/null +++ b/src/Security/test/Microsoft.AspNetCore.Authorization.Test/TestObjects/TestAuthenticationService.cs @@ -0,0 +1,58 @@ +// 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.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authorization.Test.TestObjects +{ + public class TestAuthenticationService : IAuthenticationService + { + public bool ChallengeCalled => ChallengeCount > 0; + public bool ForbidCalled => ForbidCount > 0; + public bool AuthenticateCalled => AuthenticateCount > 0; + + public int ChallengeCount { get; private set; } + public int ForbidCount { get; private set; } + public int AuthenticateCount { get; private set; } + + public Task AuthenticateAsync(HttpContext context, string scheme) + { + AuthenticateCount++; + + var identity = context.User.Identities.SingleOrDefault(i => i.AuthenticationType == scheme); + if (identity != null) + { + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(identity), scheme))); + } + + return Task.FromResult(AuthenticateResult.Fail("Denied")); + } + + public Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties) + { + ChallengeCount++; + return Task.CompletedTask; + } + + public Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties) + { + ForbidCount++; + return Task.CompletedTask; + } + + public Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) + { + throw new NotImplementedException(); + } + + public Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj b/src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj index 20cd400ce7..2f38abff90 100644 --- a/src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj +++ b/src/Security/test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test/Microsoft.AspNetCore.ChunkingCookieManager.Sources.Test.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp3.0 diff --git a/src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj b/src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj index d7a42f3efb..3dc3df2e22 100644 --- a/src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj +++ b/src/Security/test/Microsoft.AspNetCore.CookiePolicy.Test/Microsoft.AspNetCore.CookiePolicy.Test.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp3.0 diff --git a/src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs b/src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs index e2e4fd7d07..24bf09722c 100644 --- a/src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs +++ b/src/Security/test/Microsoft.Owin.Security.Interop.Test/CookieInteropTests.cs @@ -1,6 +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. +/* See https://github.com/aspnet/AspNetCore/issues/4074. + +This test is was disabled as a part of changing frameworks. This test will need to be re-written using separate .NET Core and .NET Framework processes. + using System.Collections.Generic; using System.IO; using System.Linq; @@ -330,3 +334,4 @@ namespace Microsoft.Owin.Security.Interop } } +*/ diff --git a/src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj b/src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj index f369f1f01a..57b224ef68 100644 --- a/src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj +++ b/src/Security/test/Microsoft.Owin.Security.Interop.Test/Microsoft.Owin.Security.Interop.Test.csproj @@ -2,10 +2,11 @@ net461 + + false - diff --git a/src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs b/src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs index 769adc015b..ccff1e9367 100644 --- a/src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs +++ b/src/Security/test/Microsoft.Owin.Security.Interop.Test/TicketInteropTests.cs @@ -1,6 +1,8 @@ // 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.Security.Claims; @@ -87,5 +89,6 @@ namespace Microsoft.Owin.Security.Interop.Test } } } +*/ diff --git a/src/Security/version.props b/src/Security/version.props index 4889a26987..71a78cddd8 100644 --- a/src/Security/version.props +++ b/src/Security/version.props @@ -1,7 +1,7 @@ - + - 2.2.0 - rtm + 3.0.0 + alpha1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000