From b154b00c6156512e7fbd0a2c4833c116a74f23ca Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 23 May 2022 14:00:26 -0700 Subject: [PATCH] New: Setting to disable authentication for local addresses --- .../src/Settings/General/SecuritySettings.js | 33 ++++++++++++-- .../AutomationTest.cs | 4 +- .../AuthenticationRequiredType.cs | 8 ++++ .../Authentication/AuthenticationType.cs | 5 ++- .../Configuration/ConfigFileProvider.cs | 3 ++ src/NzbDrone.Host/Startup.cs | 2 + src/NzbDrone.Test.Common/NzbDroneRunner.cs | 8 ++-- .../Config/HostConfigResource.cs | 2 + .../ApiKeyAuthenticationHandler.cs | 1 + .../AuthenticationBuilderExtensions.cs | 6 +++ ...leDenyAnonymousAuthorizationRequirement.cs | 8 ++++ .../Authentication/UiAuthorizationHandler.cs | 45 +++++++++++++++++++ .../UiAuthorizationPolicyProvider.cs | 3 +- 13 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 src/NzbDrone.Core/Authentication/AuthenticationRequiredType.cs create mode 100644 src/Sonarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs create mode 100644 src/Sonarr.Http/Authentication/UiAuthorizationHandler.cs diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js index 828222727..0950552b7 100644 --- a/frontend/src/Settings/General/SecuritySettings.js +++ b/frontend/src/Settings/General/SecuritySettings.js @@ -16,6 +16,11 @@ const authenticationMethodOptions = [ { key: 'forms', value: 'Forms (Login Page)' } ]; +const authenticationRequiredOptions = [ + { key: 'enabled', value: 'Enabled' }, + { key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' } +]; + const certificateValidationOptions = [ { key: 'enabled', value: 'Enabled' }, { key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }, @@ -67,6 +72,7 @@ class SecuritySettings extends Component { const { authenticationMethod, + authenticationRequired, username, password, apiKey, @@ -91,7 +97,24 @@ class SecuritySettings extends Component { { - authenticationEnabled && + authenticationEnabled ? + + Authentication Required + + + : + null + } + + { + authenticationEnabled ? Username @@ -101,11 +124,12 @@ class SecuritySettings extends Component { onChange={onInputChange} {...username} /> - + : + null } { - authenticationEnabled && + authenticationEnabled ? Password @@ -115,7 +139,8 @@ class SecuritySettings extends Component { onChange={onInputChange} {...password} /> - + : + null } diff --git a/src/NzbDrone.Automation.Test/AutomationTest.cs b/src/NzbDrone.Automation.Test/AutomationTest.cs index 2144b35db..f4cb204dd 100644 --- a/src/NzbDrone.Automation.Test/AutomationTest.cs +++ b/src/NzbDrone.Automation.Test/AutomationTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; @@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test _runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger()); _runner.KillAll(); - _runner.Start(); + _runner.Start(true); driver.Url = "http://localhost:8989"; diff --git a/src/NzbDrone.Core/Authentication/AuthenticationRequiredType.cs b/src/NzbDrone.Core/Authentication/AuthenticationRequiredType.cs new file mode 100644 index 000000000..dc3c2c770 --- /dev/null +++ b/src/NzbDrone.Core/Authentication/AuthenticationRequiredType.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Authentication +{ + public enum AuthenticationRequiredType + { + Enabled = 0, + DisabledForLocalAddresses = 1 + } +} diff --git a/src/NzbDrone.Core/Authentication/AuthenticationType.cs b/src/NzbDrone.Core/Authentication/AuthenticationType.cs index 9f21b07a7..ca408774b 100644 --- a/src/NzbDrone.Core/Authentication/AuthenticationType.cs +++ b/src/NzbDrone.Core/Authentication/AuthenticationType.cs @@ -1,9 +1,10 @@ -namespace NzbDrone.Core.Authentication +namespace NzbDrone.Core.Authentication { public enum AuthenticationType { None = 0, Basic = 1, - Forms = 2 + Forms = 2, + External = 3 } } diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index a2f96cd93..c68ad756e 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -31,6 +31,7 @@ namespace NzbDrone.Core.Configuration bool EnableSsl { get; } bool LaunchBrowser { get; } AuthenticationType AuthenticationMethod { get; } + AuthenticationRequiredType AuthenticationRequired { get; } bool AnalyticsEnabled { get; } string LogLevel { get; } string ConsoleLogLevel { get; } @@ -181,6 +182,8 @@ namespace NzbDrone.Core.Configuration } } + public AuthenticationRequiredType AuthenticationRequired => GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled); + public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false); public string Branch => GetValue("Branch", "main").ToLowerInvariant(); diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index 0c7cf6547..6bd4baa99 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -102,6 +102,8 @@ namespace NzbDrone.Host .PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"])); services.AddSingleton(); + services.AddSingleton(); + services.AddAuthorization(options => { options.AddPolicy("SignalR", policy => diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index a9ab77136..833285643 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -31,12 +31,12 @@ namespace NzbDrone.Test.Common Port = port; } - public void Start() + public void Start(bool enableAuth = false) { AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + TestBase.GetUID()); Directory.CreateDirectory(AppData); - GenerateConfigFile(); + GenerateConfigFile(enableAuth); string consoleExe; if (OsInfo.IsWindows) @@ -146,7 +146,7 @@ namespace NzbDrone.Test.Common } } - private void GenerateConfigFile() + private void GenerateConfigFile(bool enableAuth) { var configFile = Path.Combine(AppData, "config.xml"); @@ -159,6 +159,8 @@ namespace NzbDrone.Test.Common new XElement(nameof(ConfigFileProvider.ApiKey), apiKey), new XElement(nameof(ConfigFileProvider.LogLevel), "trace"), new XElement(nameof(ConfigFileProvider.AnalyticsEnabled), false), + new XElement(nameof(ConfigFileProvider.AuthenticationMethod), enableAuth ? "Forms" : "None"), + new XElement(nameof(ConfigFileProvider.AuthenticationRequired), "DisabledForLocalAddresses"), new XElement(nameof(ConfigFileProvider.Port), Port))); var data = xDoc.ToString(); diff --git a/src/Sonarr.Api.V3/Config/HostConfigResource.cs b/src/Sonarr.Api.V3/Config/HostConfigResource.cs index 4d0c1b3e3..b972d9b74 100644 --- a/src/Sonarr.Api.V3/Config/HostConfigResource.cs +++ b/src/Sonarr.Api.V3/Config/HostConfigResource.cs @@ -15,6 +15,7 @@ namespace Sonarr.Api.V3.Config public bool EnableSsl { get; set; } public bool LaunchBrowser { get; set; } public AuthenticationType AuthenticationMethod { get; set; } + public AuthenticationRequiredType AuthenticationRequired { get; set; } public bool AnalyticsEnabled { get; set; } public string Username { get; set; } public string Password { get; set; } @@ -56,6 +57,7 @@ namespace Sonarr.Api.V3.Config EnableSsl = model.EnableSsl, LaunchBrowser = model.LaunchBrowser, AuthenticationMethod = model.AuthenticationMethod, + AuthenticationRequired = model.AuthenticationRequired, AnalyticsEnabled = model.AnalyticsEnabled, //Username diff --git a/src/Sonarr.Http/Authentication/ApiKeyAuthenticationHandler.cs b/src/Sonarr.Http/Authentication/ApiKeyAuthenticationHandler.cs index 248390b92..864dd3644 100644 --- a/src/Sonarr.Http/Authentication/ApiKeyAuthenticationHandler.cs +++ b/src/Sonarr.Http/Authentication/ApiKeyAuthenticationHandler.cs @@ -13,6 +13,7 @@ namespace Sonarr.Http.Authentication public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions { public const string DefaultScheme = "API Key"; + public string Scheme => DefaultScheme; public string AuthenticationType = DefaultScheme; diff --git a/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs b/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs index 00013c6db..e6e0d6cd2 100644 --- a/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs +++ b/src/Sonarr.Http/Authentication/AuthenticationBuilderExtensions.cs @@ -22,10 +22,16 @@ namespace Sonarr.Http.Authentication return authenticationBuilder.AddScheme(name, options => { }); } + public static AuthenticationBuilder AddExternal(this AuthenticationBuilder authenticationBuilder, string name) + { + return authenticationBuilder.AddScheme(name, options => { }); + } + public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services) { return services.AddAuthentication() .AddNone(AuthenticationType.None.ToString()) + .AddExternal(AuthenticationType.External.ToString()) .AddBasic(AuthenticationType.Basic.ToString()) .AddCookie(AuthenticationType.Forms.ToString(), options => { diff --git a/src/Sonarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs b/src/Sonarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs new file mode 100644 index 000000000..3ad4edcba --- /dev/null +++ b/src/Sonarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authorization.Infrastructure; + +namespace NzbDrone.Http.Authentication +{ + public class BypassableDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement + { + } +} diff --git a/src/Sonarr.Http/Authentication/UiAuthorizationHandler.cs b/src/Sonarr.Http/Authentication/UiAuthorizationHandler.cs new file mode 100644 index 000000000..ead8d3885 --- /dev/null +++ b/src/Sonarr.Http/Authentication/UiAuthorizationHandler.cs @@ -0,0 +1,45 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Authentication; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Configuration.Events; +using NzbDrone.Core.Messaging.Events; +using Sonarr.Http.Extensions; + +namespace NzbDrone.Http.Authentication +{ + public class UiAuthorizationHandler : AuthorizationHandler, IAuthorizationRequirement, IHandle + { + private readonly IConfigFileProvider _configService; + private static AuthenticationRequiredType _authenticationRequired; + + public UiAuthorizationHandler(IConfigFileProvider configService) + { + _configService = configService; + _authenticationRequired = configService.AuthenticationRequired; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BypassableDenyAnonymousAuthorizationRequirement requirement) + { + if (_authenticationRequired == AuthenticationRequiredType.DisabledForLocalAddresses) + { + if (context.Resource is HttpContext httpContext && + IPAddress.TryParse(httpContext.GetRemoteIP(), out var ipAddress) && + ipAddress.IsLocalAddress()) + { + context.Succeed(requirement); + } + } + + return Task.CompletedTask; + } + + public void Handle(ConfigSavedEvent message) + { + _authenticationRequired = _configService.AuthenticationRequired; + } + } +} diff --git a/src/Sonarr.Http/Authentication/UiAuthorizationPolicyProvider.cs b/src/Sonarr.Http/Authentication/UiAuthorizationPolicyProvider.cs index a5295a99f..50f1c3ada 100644 --- a/src/Sonarr.Http/Authentication/UiAuthorizationPolicyProvider.cs +++ b/src/Sonarr.Http/Authentication/UiAuthorizationPolicyProvider.cs @@ -29,7 +29,8 @@ namespace NzbDrone.Http.Authentication if (policyName.Equals(POLICY_NAME, StringComparison.OrdinalIgnoreCase)) { var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString()) - .RequireAuthenticatedUser(); + .AddRequirements(new BypassableDenyAnonymousAuthorizationRequirement()); + return Task.FromResult(policy.Build()); }