diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1a0973a..50f0d1c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.9.0 with: - dotnet-version: 7.0.100 + dotnet-version: 8.x.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/LICENSE.txt b/LICENSE.txt index f7116cf..cf26481 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Mihir Dilip +Copyright (c) 2024 Mihir Dilip Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 282fb4e..0ca4a26 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Easy to use and very light weight Microsoft style Basic Scheme Authentication Im ## .NET (Core) Frameworks Supported .NET Framework 4.6.1 and/or NetStandard 2.0 onwards -Multi targeted: net7.0; net6.0; net5.0; netcoreapp3.1; netcoreapp3.0; netstandard2.0; net461 +Multi targeted: net8.0; net7.0; net6.0; net5.0; netcoreapp3.1; netcoreapp3.0; netstandard2.0; net461
@@ -300,6 +300,7 @@ public void ConfigureServices(IServiceCollection services) ## Release Notes | Version |           Notes | |---------|-------| +|8.0.0 | | |7.0.0 | | |6.0.1 | | |5.1.0 | | diff --git a/samples/BasicSamplesClient.http b/samples/BasicSamplesClient.http new file mode 100644 index 0000000..88c1008 --- /dev/null +++ b/samples/BasicSamplesClient.http @@ -0,0 +1,27 @@ +@HostAddress = https://localhost:44304 +@BasicHash = VGVzdFVzZXIxOjEyMzQ= + +# Get Values No Auth +GET {{HostAddress}}/api/values +Accept: application/json + +### + +# Get Values +GET {{HostAddress}}/api/values +Accept: application/json +Authorization: Basic {{BasicHash}} + +### + +# Claims +GET {{HostAddress}}/api/values/claims +Accept: application/json +Authorization: Basic {{BasicHash}} + +### + +# Forbid +GET {{HostAddress}}/api/values/forbid +Accept: application/json +Authorization: Basic {{BasicHash}} \ No newline at end of file diff --git a/samples/SampleWebApi.Shared/Models/User.cs b/samples/SampleWebApi.Shared/Models/User.cs index 02a8bb2..ad42544 100644 --- a/samples/SampleWebApi.Shared/Models/User.cs +++ b/samples/SampleWebApi.Shared/Models/User.cs @@ -1,11 +1,14 @@ -namespace SampleWebApi.Models +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace SampleWebApi.Models { /// /// NOTE: DO NOT USE THIS IMPLEMENTATION. THIS IS FOR DEMO PURPOSE ONLY /// public class User { + public string Username { get; set; } public string Password { get; set; } } } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. \ No newline at end of file diff --git a/samples/SampleWebApi.Shared/Repositories/InMemoryUserRepository.cs b/samples/SampleWebApi.Shared/Repositories/InMemoryUserRepository.cs index dd0f847..3db953c 100644 --- a/samples/SampleWebApi.Shared/Repositories/InMemoryUserRepository.cs +++ b/samples/SampleWebApi.Shared/Repositories/InMemoryUserRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using SampleWebApi.Models; @@ -10,7 +11,7 @@ namespace SampleWebApi.Repositories /// public class InMemoryUserRepository : IUserRepository { - private List _users = new List + private readonly List _users = new List { new User { Username = "TestUser1", Password = "1234" }, new User { Username = "TestUser2", Password = "1234" }, @@ -30,3 +31,4 @@ public Task> GetUsers() } } } +#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. \ No newline at end of file diff --git a/samples/SampleWebApi_2_0/SampleWebApi_2_0.csproj b/samples/SampleWebApi_2_0/SampleWebApi_2_0.csproj index 0ddd3da..716d709 100644 --- a/samples/SampleWebApi_2_0/SampleWebApi_2_0.csproj +++ b/samples/SampleWebApi_2_0/SampleWebApi_2_0.csproj @@ -2,6 +2,8 @@ netcoreapp2.0 + false + false diff --git a/samples/SampleWebApi_2_2/SampleWebApi_2_2.csproj b/samples/SampleWebApi_2_2/SampleWebApi_2_2.csproj index 77a31d3..16a743e 100644 --- a/samples/SampleWebApi_2_2/SampleWebApi_2_2.csproj +++ b/samples/SampleWebApi_2_2/SampleWebApi_2_2.csproj @@ -3,6 +3,8 @@ netcoreapp2.2 InProcess + false + false diff --git a/samples/SampleWebApi_3_1/SampleWebApi_3_1.csproj b/samples/SampleWebApi_3_1/SampleWebApi_3_1.csproj index 6c2e6c8..a952691 100644 --- a/samples/SampleWebApi_3_1/SampleWebApi_3_1.csproj +++ b/samples/SampleWebApi_3_1/SampleWebApi_3_1.csproj @@ -2,6 +2,7 @@ netcoreapp3.1 + false diff --git a/samples/SampleWebApi_5_0/SampleWebApi_5_0.csproj b/samples/SampleWebApi_5_0/SampleWebApi_5_0.csproj index 4918d4c..048820f 100644 --- a/samples/SampleWebApi_5_0/SampleWebApi_5_0.csproj +++ b/samples/SampleWebApi_5_0/SampleWebApi_5_0.csproj @@ -2,6 +2,7 @@ net5.0 + false diff --git a/samples/SampleWebApi_6_0/GlobalSuppressions.cs b/samples/SampleWebApi_6_0/GlobalSuppressions.cs new file mode 100644 index 0000000..c14c11b --- /dev/null +++ b/samples/SampleWebApi_6_0/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "", Scope = "member", Target = "~F:SampleWebApi.Repositories.InMemoryUserRepository._users")] diff --git a/samples/SampleWebApi_8_0/Controllers/ValuesController.cs b/samples/SampleWebApi_8_0/Controllers/ValuesController.cs new file mode 100644 index 0000000..5afdba5 --- /dev/null +++ b/samples/SampleWebApi_8_0/Controllers/ValuesController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using System.Text; + +namespace SampleWebApi_8_0.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + [HttpGet("claims")] + public ActionResult Claims() + { + var sb = new StringBuilder(); + foreach (var claim in User.Claims) + { + sb.AppendLine($"{claim.Type}: {claim.Value}"); + } + return sb.ToString(); + } + + [HttpGet("forbid")] + public new IActionResult Forbid() + { + return base.Forbid(); + } + } +} diff --git a/samples/SampleWebApi_8_0/Program.cs b/samples/SampleWebApi_8_0/Program.cs new file mode 100644 index 0000000..c2b5d94 --- /dev/null +++ b/samples/SampleWebApi_8_0/Program.cs @@ -0,0 +1,157 @@ +using AspNetCore.Authentication.Basic; +using Microsoft.AspNetCore.Authorization; +using SampleWebApi.Repositories; +using SampleWebApi.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add User repository to the dependency container. +builder.Services.AddTransient(); + +// Add the Basic scheme authentication here.. +// It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. +// If an implementation of IBasicUserValidationService interface is registered in the dependency register as well as OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of an implementation of IBasicUserValidationService. +builder.Services.AddAuthentication(BasicDefaults.AuthenticationScheme) + + // The below AddBasic without type parameter will require OnValidateCredentials delegete on options.Events to be set unless an implementation of IBasicUserValidationService interface is registered in the dependency register. + // Please note if both the delgate and validation server are set then the delegate will be used instead of BasicUserValidationService. + //.AddBasic(options => + + // The below AddBasic with type parameter will add the BasicUserValidationService to the dependency register. + // Please note if OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of BasicUserValidationService. + .AddBasic(options => + { + options.Realm = "Sample Web API"; + + //// Optional option to suppress the browser login dialog for ajax calls. + //options.SuppressWWWAuthenticateHeader = true; + + //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. + //options.IgnoreAuthenticationIfAllowAnonymous = true; + + //// Optional events to override the basic original logic with custom logic. + //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. + options.Events = new BasicEvents + { + + //// A delegate assigned to this property will be invoked just before validating credentials. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + // context.Success(); + // } + // else + // { + // context.NoResult(); + // } + //}, + + //// A delegate assigned to this property will be invoked just before validating credentials. + //// NOTE: Same as above delegate but slightly different implementation which will give same result. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.ValidationSucceeded(claims); // claims are optional + // } + // else + // { + // context.ValidationFailed(); + // } + //}, + + //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. + //OnHandleChallenge = async (context) => + //{ + // // custom code to handle authentication challenge unauthorized response. + // context.Response.StatusCode = StatusCodes.Status401Unauthorized; + // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. + //OnHandleForbidden = async (context) => + //{ + // // custom code to handle forbidden response. + // context.Response.StatusCode = StatusCodes.Status403Forbidden; + // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateCredentials delegate is assigned. + //// It can be used for adding claims, headers, etc to the response. + //OnAuthenticationSucceeded = (context) => + //{ + // //custom code to add extra bits to the success response. + // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); + // var customClaims = new List + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") + // }; + // context.AddClaims(customClaims); + // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); + // return Task.CompletedTask; + //}, + + //// A delegate assigned to this property will be invoked when the authentication fails. + //OnAuthenticationFailed = (context) => + //{ + // // custom code to handle failed authentication. + // context.Fail("Failed to authenticate"); + // return Task.CompletedTask; + //} + + }; + }); + +builder.Services.AddControllers(options => +{ + // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. + //options.Filters.Add(); + +}); //.AddXmlSerializerFormatters() // To enable XML along with JSON; + +// All the requests will need to be authorized. +// Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. +builder.Services.AddAuthorizationBuilder() + .SetFallbackPolicy( + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build() + ); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/samples/SampleWebApi_8_0/Properties/launchSettings.json b/samples/SampleWebApi_8_0/Properties/launchSettings.json new file mode 100644 index 0000000..5ae8c16 --- /dev/null +++ b/samples/SampleWebApi_8_0/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3920", + "sslPort": 44304 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/SampleWebApi_8_0/SampleWebApi_8_0.csproj b/samples/SampleWebApi_8_0/SampleWebApi_8_0.csproj new file mode 100644 index 0000000..f75c857 --- /dev/null +++ b/samples/SampleWebApi_8_0/SampleWebApi_8_0.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/samples/SampleWebApi_8_0/appsettings.Development.json b/samples/SampleWebApi_8_0/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/SampleWebApi_8_0/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/SampleWebApi_8_0/appsettings.json b/samples/SampleWebApi_8_0/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/SampleWebApi_8_0/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/AspNetCore.Authentication.Basic.sln b/src/AspNetCore.Authentication.Basic.sln index 1a55fdc..abf1601 100644 --- a/src/AspNetCore.Authentication.Basic.sln +++ b/src/AspNetCore.Authentication.Basic.sln @@ -5,6 +5,7 @@ VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{CF13271D-BF3F-4167-BEBA-DD02D33992F2}" ProjectSection(SolutionItems) = preProject + ..\samples\BasicSamplesClient.http = ..\samples\BasicSamplesClient.http ..\samples\BasicSamplesClient.postman_collection.json = ..\samples\BasicSamplesClient.postman_collection.json EndProjectSection EndProject @@ -33,7 +34,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Authentication.B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebApi_6_0", "..\samples\SampleWebApi_6_0\SampleWebApi_6_0.csproj", "{9232DA41-CA69-4FE3-B0C9-D8D85FEC272A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_7_0", "..\samples\SampleWebApi_7_0\SampleWebApi_7_0.csproj", "{D5C8BCC5-C997-475E-9E71-5BF807294BB6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebApi_7_0", "..\samples\SampleWebApi_7_0\SampleWebApi_7_0.csproj", "{D5C8BCC5-C997-475E-9E71-5BF807294BB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_8_0", "..\samples\SampleWebApi_8_0\SampleWebApi_8_0.csproj", "{548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -73,6 +76,10 @@ Global {D5C8BCC5-C997-475E-9E71-5BF807294BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5C8BCC5-C997-475E-9E71-5BF807294BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5C8BCC5-C997-475E-9E71-5BF807294BB6}.Release|Any CPU.Build.0 = Release|Any CPU + {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -86,6 +93,7 @@ Global {335B0D1F-A428-4D2E-AF87-269C75F5F138} = {F7494366-ED1D-4342-AE5D-DD6BE67C63DF} {9232DA41-CA69-4FE3-B0C9-D8D85FEC272A} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} {D5C8BCC5-C997-475E-9E71-5BF807294BB6} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} + {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {70815049-1680-480A-BF5A-00536D6C9C20} @@ -93,6 +101,7 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{0801aed9-ea38-4e7e-af4d-26e9b67e5254}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{2705db4c-3bce-4cfc-9a30-b4bfd1f28c56}*SharedItemsImports = 5 + ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{548dbe4a-0c06-4fb3-bea6-6ab4a0386eef}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{897e5c9c-8c0a-4fb6-960c-4d11aafd4491}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{9232da41-ca69-4fe3-b0c9-d8d85fec272a}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{b82830a0-fdfc-469d-b2a8-d657cd216451}*SharedItemsImports = 5 diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index fbcdabc..7d92820 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -1,21 +1,20 @@  - net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 - 7.0.0 + net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 + 8.0.0 https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) - aspnetcore, security, authentication, microsoft, microsoft.aspnetcore.authentication, microsoft-aspnetcore-authentication, microsoft.aspnetcore.authentication.basic, microsoft-aspnetcore-authentication-basic, asp-net-core, netstandard, netstandard20, basic-authentication, basicauthentication, dotnetcore, dotnetcore3.1, net5, net5.0, net6, net6.0, net7, net7.0, asp-net-core-basic-authentication, aspnetcore-basic-authentication, asp-net-core-authentication, aspnetcore-authentication, asp, aspnet, basic, authentication-scheme - - net7.0 support added -- Information log on handler is changed to Debug log when Authorization header is not found on the request -- Added package validations + aspnetcore, security, authentication, microsoft, microsoft.aspnetcore.authentication, microsoft-aspnetcore-authentication, microsoft.aspnetcore.authentication.basic, microsoft-aspnetcore-authentication-basic, asp-net-core, netstandard, netstandard20, basic-authentication, basicauthentication, dotnetcore, dotnetcore3.1, net5, net5.0, net6, net6.0, net7, net7.0, net8, net8.0, asp-net-core-basic-authentication, aspnetcore-basic-authentication, asp-net-core-authentication, aspnetcore-authentication, asp, aspnet, basic, authentication-scheme + - net8.0 support added +- Sample project for net8.0 added +- BasicSamplesClient.http file added for testing sample projects - Readme updated -- Readme added to package Easy to use and very light weight Microsoft style Basic Scheme Authentication implementation for ASP.NET Core. Mihir Dilip Mihir Dilip - Copyright (c) 2022 Mihir Dilip + Copyright (c) 2024 Mihir Dilip true $(AssemblyName) git @@ -31,6 +30,10 @@ + + false + + true @@ -68,7 +71,7 @@ - + diff --git a/src/AspNetCore.Authentication.Basic/BasicHandler.cs b/src/AspNetCore.Authentication.Basic/BasicHandler.cs index 7ec1b2c..50bd43c 100644 --- a/src/AspNetCore.Authentication.Basic/BasicHandler.cs +++ b/src/AspNetCore.Authentication.Basic/BasicHandler.cs @@ -20,6 +20,20 @@ namespace AspNetCore.Authentication.Basic /// public class BasicHandler : AuthenticationHandler { +#if NET8_0_OR_GREATER + /// + /// Basic Handler Constructor. + /// + /// + /// + /// + protected BasicHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : base(options, logger, encoder) + { + } + + [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] +#endif /// /// Basic Handler Constructor. /// @@ -57,13 +71,13 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - if (!Request.Headers.ContainsKey(HeaderNames.Authorization)) + if (!Request.Headers.TryGetValue(HeaderNames.Authorization, out Microsoft.Extensions.Primitives.StringValues authHeaderValues)) { Logger.LogDebug("No 'Authorization' header found in the request."); return AuthenticateResult.NoResult(); } - if (!AuthenticationHeaderValue.TryParse(Request.Headers[HeaderNames.Authorization], out var headerValue)) + if (!AuthenticationHeaderValue.TryParse(authHeaderValues, out var headerValue)) { Logger.LogInformation("No valid 'Authorization' header found in the request."); return AuthenticateResult.NoResult(); @@ -78,7 +92,7 @@ protected override async Task HandleAuthenticateAsync() BasicCredentials credentials; try { - credentials = DecodeBasicCredentials(headerValue.Parameter); + credentials = BasicHandler.DecodeBasicCredentials(headerValue.Parameter); } catch (Exception exception) { @@ -237,7 +251,7 @@ private async Task ValidateUsingBasicUserValidationServiceAsync(string use } } - private BasicCredentials DecodeBasicCredentials(string credentials) + private static BasicCredentials DecodeBasicCredentials(string credentials) { string username; string password; @@ -271,7 +285,7 @@ private BasicCredentials DecodeBasicCredentials(string credentials) return new BasicCredentials(username, password); } - private struct BasicCredentials + private readonly struct BasicCredentials { public BasicCredentials(string username, string password) { diff --git a/src/AspNetCore.Authentication.Basic/CompatibilitySuppressions.xml b/src/AspNetCore.Authentication.Basic/CompatibilitySuppressions.xml index 2d8fe59..2a0be6b 100644 --- a/src/AspNetCore.Authentication.Basic/CompatibilitySuppressions.xml +++ b/src/AspNetCore.Authentication.Basic/CompatibilitySuppressions.xml @@ -1,5 +1,12 @@  + + + CP0002 + M:AspNetCore.Authentication.Basic.BasicHandler.#ctor(Microsoft.Extensions.Options.IOptionsMonitor{AspNetCore.Authentication.Basic.BasicOptions},Microsoft.Extensions.Logging.ILoggerFactory,System.Text.Encodings.Web.UrlEncoder) + lib/net7.0/AspNetCore.Authentication.Basic.dll + lib/net8.0/AspNetCore.Authentication.Basic.dll + CP0002 M:AspNetCore.Authentication.Basic.BasicOptions.get_IgnoreAuthenticationIfAllowAnonymous diff --git a/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj b/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj index e72e5e5..d4e93aa 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj +++ b/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj @@ -1,9 +1,16 @@  - net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.1;net461 + net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.1;net461 + enable + enable false latest + true + + + + true @@ -14,50 +21,74 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -65,9 +96,14 @@ - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - + diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs index 56a8866..db5e24a 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs @@ -303,7 +303,7 @@ public void AddBasic_TBasicUserValidationService_allows_chaining_with_scheme_dis #endregion // Allows chaining - private Task GetSchemeAsync(Action authenticationBuilderAction, string schemeName = BasicDefaults.AuthenticationScheme) + private static Task GetSchemeAsync(Action authenticationBuilderAction, string schemeName = BasicDefaults.AuthenticationScheme) { var services = new ServiceCollection(); authenticationBuilderAction(services.AddAuthentication()); diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs index c9cd5b4..eeff448 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs @@ -20,8 +20,8 @@ namespace AspNetCore.Authentication.Basic.Tests { - public class BasicHandlerTests : IDisposable - { + public class BasicHandlerTests : IDisposable + { private const string HeaderFromEventsKey = nameof(HeaderFromEventsKey); private const string HeaderFromEventsValue = nameof(HeaderFromEventsValue); @@ -60,7 +60,7 @@ public async Task Verify_Handler() Assert.Equal(typeof(BasicHandler), scheme.HandlerType); var optionsSnapshot = services.GetService>(); - var options = optionsSnapshot.Get(scheme.Name); + var options = optionsSnapshot?.Get(scheme.Name); Assert.NotNull(options); Assert.NotNull(options.Events?.OnValidateCredentials); Assert.Null(options.BasicUserValidationServiceType); @@ -81,7 +81,7 @@ public async Task TBasicUserValidationService_Verify_Handler() Assert.Equal(typeof(BasicHandler), scheme.HandlerType); var optionsSnapshot = services.GetService>(); - var options = optionsSnapshot.Get(scheme.Name); + var options = optionsSnapshot?.Get(scheme.Name); Assert.NotNull(options); Assert.Null(options.Events?.OnValidateCredentials); Assert.NotNull(options.BasicUserValidationServiceType); @@ -114,7 +114,7 @@ public async Task HandleForbidden_using_OnHandleForbidden() options.Realm = TestServerBuilder.Realm; options.Events.OnHandleForbidden = context => { - context.HttpContext.Response.Headers.Add(HeaderFromEventsKey, HeaderFromEventsValue); + context.HttpContext.Response.Headers[HeaderFromEventsKey] = HeaderFromEventsValue; return Task.CompletedTask; }; }); @@ -166,7 +166,7 @@ public async Task HandleChallange_using_OnHandleChallenge() options.Realm = TestServerBuilder.Realm; options.Events.OnHandleChallenge = context => { - context.HttpContext.Response.Headers.Add(HeaderFromEventsKey, HeaderFromEventsValue); + context.HttpContext.Response.Headers[HeaderFromEventsKey] = HeaderFromEventsValue; return Task.CompletedTask; }; }); @@ -205,7 +205,7 @@ public async Task HandleChallange_using_OnHandleChallenge_and_SuppressWWWAuthent options.SuppressWWWAuthenticateHeader = true; options.Events.OnHandleChallenge = context => { - context.HttpContext.Response.Headers.Add(HeaderFromEventsKey, HeaderFromEventsValue); + context.HttpContext.Response.Headers[HeaderFromEventsKey] = HeaderFromEventsValue; return Task.CompletedTask; }; }); @@ -335,7 +335,7 @@ public async Task HandleAuthenticate_OnValidateCredentials_result_not_null() options.Realm = TestServerBuilder.Realm; options.Events.OnValidateCredentials = context => { - context.ValidationSucceeded(new List { FakeUsers.FakeRoleClaim, new Claim(ClaimTypes.Name, "my_test") }); + context.ValidationSucceeded(new List { FakeUsers.FakeRoleClaim, new(ClaimTypes.Name, "my_test") }); Assert.NotNull(context.Result); @@ -526,88 +526,88 @@ public async Task HandleAuthenticate_OnAuthenticationSucceeded_result_not_null() Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } - #endregion // HandleAuthenticate + #endregion // HandleAuthenticate - #region Multi-Scheme + #region Multi-Scheme - [Fact] - public async Task MultiScheme() - { - var claimRole = new ClaimDto(FakeUsers.FakeRoleClaim); + [Fact] + public async Task MultiScheme() + { + var claimRole = new ClaimDto(FakeUsers.FakeRoleClaim); var schemes = new List { "Scheme1", "Scheme2", }; using var server = TestServerBuilder.BuildTestServer(services => - { - services.AddAuthentication("Scheme1") - .AddBasic("Scheme1", options => - { - options.Realm = TestServerBuilder.Realm; - options.Events.OnValidateCredentials = context => - { + { + services.AddAuthentication("Scheme1") + .AddBasic("Scheme1", options => + { + options.Realm = TestServerBuilder.Realm; + options.Events.OnValidateCredentials = context => + { var user = FakeUsers.Users.FirstOrDefault(u => u.Username.Equals(context.Username, StringComparison.OrdinalIgnoreCase) && u.Password.Equals(context.Password, StringComparison.OrdinalIgnoreCase)); if (user != null) { - context.Response.Headers.Add("X-Custom", "Scheme1"); + context.Response.Headers["X-Custom"] = "Scheme1"; context.ValidationSucceeded(new List { FakeUsers.FakeRoleClaim }); } - else - { + else + { context.ValidationFailed(); } - return Task.CompletedTask; - }; - }) - .AddBasic("Scheme2", options => - { - options.Realm = TestServerBuilder.Realm; - }); + return Task.CompletedTask; + }; + }) + .AddBasic("Scheme2", options => + { + options.Realm = TestServerBuilder.Realm; + }); #if !(NET461 || NETSTANDARD2_0 || NETCOREAPP2_1) services.Configure(options => options.FallbackPolicy = new AuthorizationPolicyBuilder(schemes.ToArray()).RequireAuthenticatedUser().Build()); #endif }); - using var client = server.CreateClient(); + using var client = server.CreateClient(); - using var request1 = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl + "?scheme=" + schemes[0]); + using var request1 = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl + "?scheme=" + schemes[0]); request1.Headers.Authorization = FakeUsers.FakeUser.ToAuthenticationHeaderValue(); using var response1 = await client.SendAsync(request1); - Assert.True(response1.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, response1.StatusCode); - var response1Principal = await DeserializeClaimsPrincipalAsync(response1); - Assert.Contains(response1.Headers, r => r.Key == "X-Custom" && r.Value.Any(v => v == "Scheme1")); - Assert.Contains(response1Principal.Claims, c => c.Type == claimRole.Type && c.Value == claimRole.Value); + Assert.True(response1.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response1.StatusCode); + var response1Principal = await DeserializeClaimsPrincipalAsync(response1); + Assert.Contains(response1.Headers, r => r.Key == "X-Custom" && r.Value.Any(v => v == "Scheme1")); + Assert.Contains(response1Principal.Claims, c => c.Type == claimRole.Type && c.Value == claimRole.Value); - using var request2 = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl + "?scheme=" + schemes[1]); + using var request2 = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl + "?scheme=" + schemes[1]); request2.Headers.Authorization = new User("test", "test").ToAuthenticationHeaderValue(); using var response2 = await client.SendAsync(request2); - Assert.True(response2.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, response2.StatusCode); - var response2Principal = await DeserializeClaimsPrincipalAsync(response2); - Assert.DoesNotContain(response2.Headers, r => r.Key == "X-Custom" && r.Value.Any(v => v == "Scheme1")); - Assert.DoesNotContain(response2Principal.Claims, c => c.Type == claimRole.Type && c.Value == claimRole.Value); - } + Assert.True(response2.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response2.StatusCode); + var response2Principal = await DeserializeClaimsPrincipalAsync(response2); + Assert.DoesNotContain(response2.Headers, r => r.Key == "X-Custom" && r.Value.Any(v => v == "Scheme1")); + Assert.DoesNotContain(response2Principal.Claims, c => c.Type == claimRole.Type && c.Value == claimRole.Value); + } - #endregion // Multi-Scheme + #endregion // Multi-Scheme - private async Task DeserializeClaimsPrincipalAsync(HttpResponseMessage response) - { + private async Task DeserializeClaimsPrincipalAsync(HttpResponseMessage response) + { return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); } - private class FakeBasicUserValidationServiceLocal_1 : IBasicUserValidationService - { - public Task IsValidAsync(string username, string password) - { + private class FakeBasicUserValidationServiceLocal_1 : IBasicUserValidationService + { + public Task IsValidAsync(string username, string password) + { return Task.FromResult(true); - } - } + } + } private class FakeBasicUserValidationServiceLocal_2 : IBasicUserValidationService { - public Task IsValidAsync(string username, string password) - { + public Task IsValidAsync(string username, string password) + { return Task.FromResult(true); } } diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs index 81af794..47a4abf 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs @@ -83,7 +83,7 @@ public void BasicUserValidationServiceType_verify_null() var services = server.Host.Services; var apiKeyOptionsSnapshot = services.GetService>(); - var apiKeyOptions = apiKeyOptionsSnapshot.Get(BasicDefaults.AuthenticationScheme); + var apiKeyOptions = apiKeyOptionsSnapshot?.Get(BasicDefaults.AuthenticationScheme); Assert.NotNull(apiKeyOptions); Assert.Null(apiKeyOptions.BasicUserValidationServiceType); @@ -98,7 +98,7 @@ public void BasicUserValidationServiceType_verify_not_null() var services = server.Host.Services; var apiKeyOptionsSnapshot = services.GetService>(); - var apiKeyOptions = apiKeyOptionsSnapshot.Get(BasicDefaults.AuthenticationScheme); + var apiKeyOptions = apiKeyOptionsSnapshot?.Get(BasicDefaults.AuthenticationScheme); Assert.NotNull(apiKeyOptions); Assert.NotNull(apiKeyOptions.BasicUserValidationServiceType); Assert.Equal(typeof(FakeBasicUserValidationService), apiKeyOptions.BasicUserValidationServiceType); diff --git a/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs b/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..7f3f9c8 --- /dev/null +++ b/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; +[assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.ClaimsIdentityDto.#ctor(System.Security.Principal.IIdentity)")] +[assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.TestServerBuilder.BuildTestServer(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder})~Microsoft.AspNetCore.TestHost.TestServer")] +[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.ClaimsPrincipalDto.#ctor(System.Security.Claims.ClaimsPrincipal)")] +[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.ClaimDto.#ctor(System.Security.Claims.Claim)")] +[assembly: SuppressMessage("Usage", "ASP0025:Use AddAuthorizationBuilder", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.TestServerBuilder.BuildTestServer(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder})~Microsoft.AspNetCore.TestHost.TestServer")] diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs index 3b96e87..543b870 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs @@ -27,16 +27,17 @@ public ClaimsPrincipalDto(ClaimsPrincipal user) [Serializable] struct ClaimsIdentityDto { - public ClaimsIdentityDto(IIdentity identity) + public ClaimsIdentityDto(IIdentity? identity) { + if (identity == null) throw new ArgumentNullException(nameof(identity)); Name = identity.Name; IsAuthenticated = identity.IsAuthenticated; AuthenticationType = identity.AuthenticationType; } - public string Name { get; set; } + public string? Name { get; set; } public bool IsAuthenticated { get; set; } - public string AuthenticationType { get; set; } + public string? AuthenticationType { get; set; } } [Serializable] diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs index 549116a..273eec4 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs @@ -35,17 +35,11 @@ public Task IsValidAsync(string username, string password) } } - class User + class User(string username, string password, IReadOnlyCollection? claims = null) { - public User(string username, string password, IReadOnlyCollection claims = null) - { - Username = username; - Password = password; - Claims = claims; - } - public string Username { get; } - public string Password { get; } - public IReadOnlyCollection Claims { get; } + public string Username { get; } = username; + public string Password { get; } = password; + public IReadOnlyCollection? Claims { get; } = claims; public AuthenticationHeaderValue ToAuthenticationHeaderValue() { @@ -61,16 +55,16 @@ class FakeUsers internal static string FakePasswordThrowsNotImplemented = "myrandomfakepassowrd-not-implemented"; internal static string FakePasswordIgnoreAuthenticationIfAllowAnonymous = "IgnoreAuthenticationIfAllowAnonymous"; internal static string FakeUserOwner = "Fake Owner"; - internal static Claim FakeNameClaim = new Claim(ClaimTypes.Name, FakeUserName, ClaimValueTypes.String); - internal static Claim FakeNameIdentifierClaim = new Claim(ClaimTypes.NameIdentifier, FakeUserName, ClaimValueTypes.String); - internal static Claim FakeRoleClaim = new Claim(ClaimTypes.Role, "FakeRoleClaim", ClaimValueTypes.String); + internal static Claim FakeNameClaim = new(ClaimTypes.Name, FakeUserName, ClaimValueTypes.String); + internal static Claim FakeNameIdentifierClaim = new(ClaimTypes.NameIdentifier, FakeUserName, ClaimValueTypes.String); + internal static Claim FakeRoleClaim = new(ClaimTypes.Role, "FakeRoleClaim", ClaimValueTypes.String); - internal static User FakeUser => new User(FakeUserName, FakePassword, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); - internal static User FakeUserWithEmptyPassword => new User(FakeUserName, string.Empty, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); - internal static User FakeUserThrowsNotImplemented => new User(FakeUserName, FakePasswordThrowsNotImplemented, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); - internal static User FakeUserIgnoreAuthenticationIfAllowAnonymous => new User(FakeUserName, FakePasswordIgnoreAuthenticationIfAllowAnonymous, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); + internal static User FakeUser => new(FakeUserName, FakePassword, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); + internal static User FakeUserWithEmptyPassword => new(FakeUserName, string.Empty, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); + internal static User FakeUserThrowsNotImplemented => new(FakeUserName, FakePasswordThrowsNotImplemented, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); + internal static User FakeUserIgnoreAuthenticationIfAllowAnonymous => new(FakeUserName, FakePasswordIgnoreAuthenticationIfAllowAnonymous, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); - internal static List Users => new List + internal static List Users => new() { FakeUser, FakeUserWithEmptyPassword, diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs index 0bc270c..7c9e919 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs @@ -24,7 +24,7 @@ partial class TestServerBuilder internal static string ClaimsPrincipalUrl = $"{BaseUrl}claims-principal"; internal static string Realm = "BasicTests"; - internal static TestServer BuildTestServer(Action configureOptions = null) + internal static TestServer BuildTestServer(Action? configureOptions = null) { return BuildTestServer( services => @@ -35,7 +35,7 @@ internal static TestServer BuildTestServer(Action configureOptions ); } - internal static TestServer BuildTestServerWithService(Action configureOptions = null) + internal static TestServer BuildTestServerWithService(Action? configureOptions = null) { return BuildTestServer( services => @@ -46,7 +46,7 @@ internal static TestServer BuildTestServerWithService(Action confi ); } - internal static TestServer BuildTestServer(Action configureServices, Action configure = null) + internal static TestServer BuildTestServer(Action configureServices, Action? configure = null) { if (configureServices == null) throw new ArgumentNullException(nameof(configureServices));