From 2cf3940f4a8b855d59757ec806082d40d3aae302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Sm=C3=B3=C5=82ka?= Date: Sat, 20 Jan 2018 12:42:00 +0100 Subject: [PATCH] Change the way how authnetication is handled. Now by assigning Principal to context. Allowing events to be used by dependency injection #4. --- README.md | 17 ++- .../BasicAuthenticationExtensions.cs | 16 +-- .../BasicAuthenticationHandler.cs | 27 +++- .../Events/BasicAuthenticationEvents.cs | 13 +- .../Events/ValidatePrincipalContext.cs | 5 + ...tCS.AspNetCore.Authentication.Basic.csproj | 10 +- .../AuthorizationTest.cs | 128 +++++++++++++----- .../BasicAuthenticationEventsTest.cs | 50 +++++++ .../TestController.cs | 5 +- .../WebHostBuilderHelper.cs | 49 +++++-- ...spNetCore.Authentication.BasicTests.csproj | 15 +- 11 files changed, 243 insertions(+), 92 deletions(-) create mode 100644 test/ZNetCS.AspNetCore.Authentication.BasicTests/BasicAuthenticationEventsTest.cs diff --git a/README.md b/README.md index 5357b30..e5554d3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ Install using the [ZNetCS.AspNetCore.Authentication.Basic NuGet package](https:/ PM> Install-Package ZNetCS.AspNetCore.Authentication.Basic ``` +# Important change in 3.0.0 +The `OnValidatePrincipal` will not return `AuthenticationResult` any more. To simplify process can simply return `Task.CompletedTask`. +Also to make success authentication `Principal` have to be assigned to `ValidatePrincipalContext` context. + ## Usage When you install the package, it should be added to your `.csproj`. Alternatively, you can add it directly by adding: @@ -19,7 +23,7 @@ When you install the package, it should be added to your `.csproj`. Alternativel ```xml - + ``` @@ -64,15 +68,14 @@ public void ConfigureServices(IServiceCollection services) new Claim(ClaimTypes.Name, context.UserName, context.Options.ClaimsIssuer) }; - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, BasicAuthenticationDefaults.AuthenticationScheme)), - new Microsoft.AspNetCore.Authentication.AuthenticationProperties(), - BasicAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, BasicAuthenticationDefaults.AuthenticationScheme)); + context.Principal = principal; - return Task.FromResult(AuthenticateResult.Success(ticket)); + // optional with following default. + // context.AuthenticationFailMessage = "Authentication failed."; } - return Task.FromResult(AuthenticateResult.Fail("Authentication failed.")); + return Task.CompletedTask; } }; }); diff --git a/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationExtensions.cs b/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationExtensions.cs index be196bc..76665de 100644 --- a/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationExtensions.cs +++ b/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationExtensions.cs @@ -31,9 +31,7 @@ public static class BasicAuthenticationExtensions /// The authentication builder. /// public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder) - { - return builder.AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme); - } + => builder.AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme); /// /// Adds basic authentication. @@ -45,9 +43,7 @@ public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBu /// The authentication scheme. /// public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder, string authenticationScheme) - { - return builder.AddBasicAuthentication(authenticationScheme, null); - } + => builder.AddBasicAuthentication(authenticationScheme, null); /// /// Adds basic authentication. @@ -59,9 +55,7 @@ public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBu /// The configure options. /// public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder, Action configureOptions) - { - return builder.AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); - } + => builder.AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); /// /// Adds basic authentication. @@ -79,9 +73,7 @@ public static AuthenticationBuilder AddBasicAuthentication( this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) - { - return builder.AddScheme(authenticationScheme, configureOptions); - } + => builder.AddScheme(authenticationScheme, configureOptions); #endregion } diff --git a/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationHandler.cs b/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationHandler.cs index f1cfc99..49a436c 100644 --- a/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationHandler.cs +++ b/src/ZNetCS.AspNetCore.Authentication.Basic/BasicAuthenticationHandler.cs @@ -78,8 +78,26 @@ public BasicAuthenticationHandler(IOptionsMonitor op #endregion + #region Properties + + /// + /// Gets or sets the events. The handler calls methods on the events which give the application control + /// at certain points where processing is occurring. If it is not provided a default instance + /// is supplied which does nothing when the methods are called. + /// + protected new BasicAuthenticationEvents Events + { + get => (BasicAuthenticationEvents)base.Events; + set => base.Events = value; + } + + #endregion + #region Methods + /// + protected override Task CreateEventsAsync() => Task.FromResult(new BasicAuthenticationEvents()); + /// protected override async Task HandleAuthenticateAsync() { @@ -131,7 +149,14 @@ protected override async Task HandleAuthenticateAsync() string password = decodedCredentials.Substring(delimiterIndex + 1); var context = new ValidatePrincipalContext(this.Context, this.Scheme, this.Options, userName, password); - return await this.Options.Events.ValidatePrincipal(context); + await this.Events.ValidatePrincipalAsync(context); + + if (context.Principal == null) + { + return AuthenticateResult.Fail(context.AuthenticationFailMessage); + } + + return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, this.Scheme.Name)); } /// diff --git a/src/ZNetCS.AspNetCore.Authentication.Basic/Events/BasicAuthenticationEvents.cs b/src/ZNetCS.AspNetCore.Authentication.Basic/Events/BasicAuthenticationEvents.cs index 273cea5..c787673 100644 --- a/src/ZNetCS.AspNetCore.Authentication.Basic/Events/BasicAuthenticationEvents.cs +++ b/src/ZNetCS.AspNetCore.Authentication.Basic/Events/BasicAuthenticationEvents.cs @@ -14,8 +14,6 @@ namespace ZNetCS.AspNetCore.Authentication.Basic.Events using System; using System.Threading.Tasks; - using Microsoft.AspNetCore.Authentication; - #endregion /// @@ -28,14 +26,11 @@ public class BasicAuthenticationEvents /// /// Gets or sets a delegate assigned to this property will be invoked when the related method is called. /// - public Func> OnValidatePrincipal { get; set; } = - context => Task.FromResult(AuthenticateResult.Fail("Incorrect credentials.")); + public Func OnValidatePrincipal { get; set; } = context => Task.CompletedTask; #endregion - #region Implemented Interfaces - - #region IBasicAuthenticationEvents + #region Public Methods /// /// Called each time a request principal has been validated by the middleware. By implementing this method the @@ -47,9 +42,7 @@ public class BasicAuthenticationEvents /// /// A representing the completed operation. /// - public virtual Task ValidatePrincipal(ValidatePrincipalContext context) => this.OnValidatePrincipal(context); - - #endregion + public virtual Task ValidatePrincipalAsync(ValidatePrincipalContext context) => this.OnValidatePrincipal(context); #endregion } diff --git a/src/ZNetCS.AspNetCore.Authentication.Basic/Events/ValidatePrincipalContext.cs b/src/ZNetCS.AspNetCore.Authentication.Basic/Events/ValidatePrincipalContext.cs index ebd4477..5a07831 100644 --- a/src/ZNetCS.AspNetCore.Authentication.Basic/Events/ValidatePrincipalContext.cs +++ b/src/ZNetCS.AspNetCore.Authentication.Basic/Events/ValidatePrincipalContext.cs @@ -55,6 +55,11 @@ public ValidatePrincipalContext(HttpContext context, AuthenticationScheme scheme #region Public Properties + /// + /// Gets or sets the authentication fail message. + /// + public string AuthenticationFailMessage { get; set; } = "Authentication failed."; + /// /// Gets the password. /// diff --git a/src/ZNetCS.AspNetCore.Authentication.Basic/ZNetCS.AspNetCore.Authentication.Basic.csproj b/src/ZNetCS.AspNetCore.Authentication.Basic/ZNetCS.AspNetCore.Authentication.Basic.csproj index b0ed579..2e364b0 100644 --- a/src/ZNetCS.AspNetCore.Authentication.Basic/ZNetCS.AspNetCore.Authentication.Basic.csproj +++ b/src/ZNetCS.AspNetCore.Authentication.Basic/ZNetCS.AspNetCore.Authentication.Basic.csproj @@ -13,7 +13,7 @@ https://raw.githubusercontent.com/msmolka/ZNetCS.AspNetCore.Authentication.Basic/master/LICENSE git https://github.com/msmolka/ZNetCS.AspNetCore.Authentication.Basic - 2.0.0 + 3.0.0 false false false @@ -37,13 +37,13 @@ - - + + - - + + All diff --git a/test/ZNetCS.AspNetCore.Authentication.BasicTests/AuthorizationTest.cs b/test/ZNetCS.AspNetCore.Authentication.BasicTests/AuthorizationTest.cs index d6bb961..d0afcaf 100644 --- a/test/ZNetCS.AspNetCore.Authentication.BasicTests/AuthorizationTest.cs +++ b/test/ZNetCS.AspNetCore.Authentication.BasicTests/AuthorizationTest.cs @@ -34,45 +34,45 @@ public class AuthorizationTest #region Public Methods /// - /// The unauthorized basic realm. + /// The authorized valid credentials. /// [TestMethod] - public async Task UnauthorizedBasicRealmTest() + public async Task AuthorizedCredentialsTest() { - using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { }))) + using (var server = new TestServer(WebHostBuilderHelper.CreateStartupBuilder())) { using (HttpClient client = server.CreateClient()) { + // Arrange + client.DefaultRequestHeaders.Add(HeaderNames.Authorization, AuthorizationHeaderHelper.GetBasic()); + // Act HttpResponseMessage response = await client.GetAsync("api/test"); // Assert - AuthenticationHeaderValue wwwAuth = response.Headers.WwwAuthenticate.Single(); - NameValueHeaderValue nvh = NameValueHeaderValue.Parse(wwwAuth.Parameter); - - Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); - Assert.AreEqual("Basic", wwwAuth.Scheme, "Scheme != Basic"); - Assert.AreEqual("realm", nvh.Name, "!realm"); - Assert.AreEqual("\"Basic Realm\"", nvh.Value, "!basic realm"); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); } } } /// - /// The unauthorized. + /// The authorized valid credentials. /// [TestMethod] - public async Task UnauthorizedBasicTest() + public async Task AuthorizedCredentialsTestWithDi() { - using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { }))) + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder())) { using (HttpClient client = server.CreateClient()) { + // Arrange + client.DefaultRequestHeaders.Add(HeaderNames.Authorization, AuthorizationHeaderHelper.GetBasic()); + // Act HttpResponseMessage response = await client.GetAsync("api/test"); // Assert - Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); } } } @@ -81,9 +81,9 @@ public async Task UnauthorizedBasicTest() /// The unauthorized basic realm. /// [TestMethod] - public async Task UnauthorizedMyRealmTest() + public async Task UnauthorizedBasicRealmTestWithDi() { - using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { o.Realm = "My realm"; }))) + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder())) { using (HttpClient client = server.CreateClient()) { @@ -97,24 +97,21 @@ public async Task UnauthorizedMyRealmTest() Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); Assert.AreEqual("Basic", wwwAuth.Scheme, "Scheme != Basic"); Assert.AreEqual("realm", nvh.Name, "!realm"); - Assert.AreEqual("\"My realm\"", nvh.Value, "!My realm"); + Assert.AreEqual("\"Basic Realm\"", nvh.Value, "!basic realm"); } } } /// - /// The unauthorized vapid credentials but not setup. + /// The unauthorized basic realm. /// [TestMethod] - public async Task UnauthorizedValidCredentialsTest() + public async Task UnauthorizedBasicRealmTestWithOptions() { using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { }))) { using (HttpClient client = server.CreateClient()) { - // Arrange - client.DefaultRequestHeaders.Add(HeaderNames.Authorization, AuthorizationHeaderHelper.GetBasic()); - // Act HttpResponseMessage response = await client.GetAsync("api/test"); @@ -130,6 +127,44 @@ public async Task UnauthorizedValidCredentialsTest() } } + /// + /// The unauthorized. + /// + [TestMethod] + public async Task UnauthorizedBasicTestWithDi() + { + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder())) + { + using (HttpClient client = server.CreateClient()) + { + // Act + HttpResponseMessage response = await client.GetAsync("api/test"); + + // Assert + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); + } + } + } + + /// + /// The unauthorized. + /// + [TestMethod] + public async Task UnauthorizedBasicTestWithOptions() + { + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { }))) + { + using (HttpClient client = server.CreateClient()) + { + // Act + HttpResponseMessage response = await client.GetAsync("api/test"); + + // Assert + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); + } + } + } + /// /// The unauthorized invalid credentials realm. /// @@ -159,17 +194,42 @@ public async Task UnauthorizedInvalidCredentialsTest() } /// - /// The unauthorized invalid credentials realm. + /// The unauthorized basic realm. /// [TestMethod] - public async Task UnauthorizedWrongHeaderTest() + public async Task UnauthorizedMyRealmTest() { - using (var server = new TestServer(WebHostBuilderHelper.CreateStartupBuilder())) + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { o.Realm = "My realm"; }))) + { + using (HttpClient client = server.CreateClient()) + { + // Act + HttpResponseMessage response = await client.GetAsync("api/test"); + + // Assert + AuthenticationHeaderValue wwwAuth = response.Headers.WwwAuthenticate.Single(); + NameValueHeaderValue nvh = NameValueHeaderValue.Parse(wwwAuth.Parameter); + + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); + Assert.AreEqual("Basic", wwwAuth.Scheme, "Scheme != Basic"); + Assert.AreEqual("realm", nvh.Name, "!realm"); + Assert.AreEqual("\"My realm\"", nvh.Value, "!My realm"); + } + } + } + + /// + /// The unauthorized vapid credentials but not setup. + /// + [TestMethod] + public async Task UnauthorizedValidCredentialsTestWithOptions() + { + using (var server = new TestServer(WebHostBuilderHelper.CreateBuilder(o => { }))) { using (HttpClient client = server.CreateClient()) { // Arrange - client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic"); + client.DefaultRequestHeaders.Add(HeaderNames.Authorization, AuthorizationHeaderHelper.GetBasic()); // Act HttpResponseMessage response = await client.GetAsync("api/test"); @@ -181,29 +241,35 @@ public async Task UnauthorizedWrongHeaderTest() Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); Assert.AreEqual("Basic", wwwAuth.Scheme, "Scheme != Basic"); Assert.AreEqual("realm", nvh.Name, "!realm"); - Assert.AreEqual("\"My realm\"", nvh.Value, "!My realm"); + Assert.AreEqual("\"Basic Realm\"", nvh.Value, "!basic realm"); } } } /// - /// The authorized valid credentials. + /// The unauthorized invalid credentials realm. /// [TestMethod] - public async Task AuthorizedCredentialsTest() + public async Task UnauthorizedWrongHeaderTest() { using (var server = new TestServer(WebHostBuilderHelper.CreateStartupBuilder())) { using (HttpClient client = server.CreateClient()) { // Arrange - client.DefaultRequestHeaders.Add(HeaderNames.Authorization, AuthorizationHeaderHelper.GetBasic()); + client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic"); // Act HttpResponseMessage response = await client.GetAsync("api/test"); // Assert - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); + AuthenticationHeaderValue wwwAuth = response.Headers.WwwAuthenticate.Single(); + NameValueHeaderValue nvh = NameValueHeaderValue.Parse(wwwAuth.Parameter); + + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "StatusCode != Unauthorized"); + Assert.AreEqual("Basic", wwwAuth.Scheme, "Scheme != Basic"); + Assert.AreEqual("realm", nvh.Name, "!realm"); + Assert.AreEqual("\"My realm\"", nvh.Value, "!My realm"); } } } diff --git a/test/ZNetCS.AspNetCore.Authentication.BasicTests/BasicAuthenticationEventsTest.cs b/test/ZNetCS.AspNetCore.Authentication.BasicTests/BasicAuthenticationEventsTest.cs new file mode 100644 index 0000000..705c5cc --- /dev/null +++ b/test/ZNetCS.AspNetCore.Authentication.BasicTests/BasicAuthenticationEventsTest.cs @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Marcin Smółka zNET Computer Solutions. All rights reserved. +// +// +// The basic authentication events test. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ZNetCS.AspNetCore.Authentication.BasicTests +{ + #region Usings + + using System.Collections.Generic; + using System.Security.Claims; + using System.Threading.Tasks; + + using ZNetCS.AspNetCore.Authentication.Basic; + using ZNetCS.AspNetCore.Authentication.Basic.Events; + + #endregion + + /// + /// The basic authentication events test. + /// + public class BasicAuthenticationEventsTest : BasicAuthenticationEvents + { + #region Public Methods + + /// + public override Task ValidatePrincipalAsync(ValidatePrincipalContext context) + { + if ((context.UserName == "userName") && (context.Password == "password")) + { + var claims = new List + { + new Claim(ClaimTypes.Name, context.UserName, context.Options.ClaimsIssuer) + }; + + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, BasicAuthenticationDefaults.AuthenticationScheme)); + context.Principal = principal; + context.AuthenticationFailMessage = null; + } + + return Task.CompletedTask; + } + + #endregion + } +} \ No newline at end of file diff --git a/test/ZNetCS.AspNetCore.Authentication.BasicTests/TestController.cs b/test/ZNetCS.AspNetCore.Authentication.BasicTests/TestController.cs index 555257c..302b854 100644 --- a/test/ZNetCS.AspNetCore.Authentication.BasicTests/TestController.cs +++ b/test/ZNetCS.AspNetCore.Authentication.BasicTests/TestController.cs @@ -29,10 +29,7 @@ public class TestController : Controller /// The test get. /// [HttpGet] - public IActionResult Get() - { - return new OkResult(); - } + public IActionResult Get() => new OkResult(); #endregion } diff --git a/test/ZNetCS.AspNetCore.Authentication.BasicTests/WebHostBuilderHelper.cs b/test/ZNetCS.AspNetCore.Authentication.BasicTests/WebHostBuilderHelper.cs index 35077d1..c1b4093 100644 --- a/test/ZNetCS.AspNetCore.Authentication.BasicTests/WebHostBuilderHelper.cs +++ b/test/ZNetCS.AspNetCore.Authentication.BasicTests/WebHostBuilderHelper.cs @@ -17,7 +17,6 @@ namespace ZNetCS.AspNetCore.Authentication.BasicTests using System.Security.Claims; using System.Threading.Tasks; - using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -41,13 +40,8 @@ public class WebHostBuilderHelper /// /// The configure options action. /// - public static IWebHostBuilder CreateBuilder(Action configureOptions = null) + public static IWebHostBuilder CreateBuilder(Action configureOptions) { - if (configureOptions == null) - { - configureOptions = ConfigureOptions(); - } - IWebHostBuilder builder = new WebHostBuilder() .ConfigureServices( s => @@ -72,6 +66,36 @@ public static IWebHostBuilder CreateBuilder(Action c return builder; } + /// + /// Creates code builder. + /// + public static IWebHostBuilder CreateBuilder() + { + IWebHostBuilder builder = new WebHostBuilder() + .ConfigureServices( + s => + { + s.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme).AddBasicAuthentication(options => options.EventsType = typeof(BasicAuthenticationEventsTest)); + s.AddSingleton(); + s.AddMvc(); + }) + .Configure( + app => + { + app.UseAuthentication(); + app.UseMvc(); + }) + .ConfigureLogging( + (context, logging) => + { + logging + .AddFilter("Default", LogLevel.Debug) + .AddDebug(); + }); + + return builder; + } + /// /// Creates options. /// @@ -91,15 +115,12 @@ public static Action ConfigureOptions() new Claim(ClaimTypes.Name, context.UserName, context.Options.ClaimsIssuer) }; - var ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, BasicAuthenticationDefaults.AuthenticationScheme)), - new AuthenticationProperties(), - BasicAuthenticationDefaults.AuthenticationScheme); - - return Task.FromResult(AuthenticateResult.Success(ticket)); + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, BasicAuthenticationDefaults.AuthenticationScheme)); + context.Principal = principal; + context.AuthenticationFailMessage = null; } - return Task.FromResult(AuthenticateResult.Fail("Authentication failed.")); + return Task.CompletedTask; } }; }; diff --git a/test/ZNetCS.AspNetCore.Authentication.BasicTests/ZNetCS.AspNetCore.Authentication.BasicTests.csproj b/test/ZNetCS.AspNetCore.Authentication.BasicTests/ZNetCS.AspNetCore.Authentication.BasicTests.csproj index a155b69..37cfe80 100644 --- a/test/ZNetCS.AspNetCore.Authentication.BasicTests/ZNetCS.AspNetCore.Authentication.BasicTests.csproj +++ b/test/ZNetCS.AspNetCore.Authentication.BasicTests/ZNetCS.AspNetCore.Authentication.BasicTests.csproj @@ -22,16 +22,15 @@ - - - - + + + + - - - - + + + All