diff --git a/frontend/src/Settings/General/SecuritySettings.js b/frontend/src/Settings/General/SecuritySettings.js
index bb20a9305..d3946b5bd 100644
--- a/frontend/src/Settings/General/SecuritySettings.js
+++ b/frontend/src/Settings/General/SecuritySettings.js
@@ -17,6 +17,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' },
@@ -68,6 +73,7 @@ class SecuritySettings extends Component {
const {
authenticationMethod,
+ authenticationRequired,
username,
password,
apiKey,
@@ -94,7 +100,24 @@ class SecuritySettings extends Component {
{
- authenticationEnabled &&
+ authenticationEnabled ?
+
+ Authentication Required
+
+
+ :
+ null
+ }
+
+ {
+ authenticationEnabled ?
{translate('Username')}
@@ -106,11 +129,12 @@ class SecuritySettings extends Component {
onChange={onInputChange}
{...username}
/>
-
+ :
+ null
}
{
- authenticationEnabled &&
+ authenticationEnabled ?
{translate('Password')}
@@ -122,7 +146,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 615e73cb6..3351ceaac 100644
--- a/src/NzbDrone.Automation.Test/AutomationTest.cs
+++ b/src/NzbDrone.Automation.Test/AutomationTest.cs
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll();
- _runner.Start();
+ _runner.Start(true);
driver.Url = "http://localhost:8787";
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 94eaa67a8..9765cdccd 100644
--- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs
@@ -32,6 +32,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; }
@@ -190,6 +191,8 @@ namespace NzbDrone.Core.Configuration
}
}
+ public AuthenticationRequiredType AuthenticationRequired => GetValueEnum("AuthenticationRequired", AuthenticationRequiredType.Enabled);
+
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
// TODO: Change back to "master" for the first stable release
diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs
index b14dc14b4..766a9c8bd 100644
--- a/src/NzbDrone.Host/Startup.cs
+++ b/src/NzbDrone.Host/Startup.cs
@@ -171,6 +171,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 a84864c5d..b387e24b9 100644
--- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs
+++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs
@@ -38,12 +38,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 readarrConsoleExe;
if (OsInfo.IsWindows)
@@ -178,7 +178,7 @@ namespace NzbDrone.Test.Common
}
}
- private void GenerateConfigFile()
+ private void GenerateConfigFile(bool enableAuth)
{
var configFile = Path.Combine(AppData, "config.xml");
@@ -191,6 +191,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/Readarr.Api.V1/Config/HostConfigResource.cs b/src/Readarr.Api.V1/Config/HostConfigResource.cs
index 7ca7393ea..ecd4b8ee0 100644
--- a/src/Readarr.Api.V1/Config/HostConfigResource.cs
+++ b/src/Readarr.Api.V1/Config/HostConfigResource.cs
@@ -15,6 +15,7 @@ namespace Readarr.Api.V1.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; }
@@ -57,6 +58,7 @@ namespace Readarr.Api.V1.Config
EnableSsl = model.EnableSsl,
LaunchBrowser = model.LaunchBrowser,
AuthenticationMethod = model.AuthenticationMethod,
+ AuthenticationRequired = model.AuthenticationRequired,
AnalyticsEnabled = model.AnalyticsEnabled,
//Username
diff --git a/src/Readarr.Http/Authentication/ApiKeyAuthenticationHandler.cs b/src/Readarr.Http/Authentication/ApiKeyAuthenticationHandler.cs
index f0b35afdc..c41eb55be 100644
--- a/src/Readarr.Http/Authentication/ApiKeyAuthenticationHandler.cs
+++ b/src/Readarr.Http/Authentication/ApiKeyAuthenticationHandler.cs
@@ -13,6 +13,7 @@ namespace Readarr.Http.Authentication
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "API Key";
+
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
diff --git a/src/Readarr.Http/Authentication/AuthenticationBuilderExtensions.cs b/src/Readarr.Http/Authentication/AuthenticationBuilderExtensions.cs
index e0d78555d..0507830d9 100644
--- a/src/Readarr.Http/Authentication/AuthenticationBuilderExtensions.cs
+++ b/src/Readarr.Http/Authentication/AuthenticationBuilderExtensions.cs
@@ -22,10 +22,16 @@ namespace Readarr.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/Readarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs b/src/Readarr.Http/Authentication/BypassableDenyAnonymousAuthorizationRequirement.cs
new file mode 100644
index 000000000..3ad4edcba
--- /dev/null
+++ b/src/Readarr.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/Readarr.Http/Authentication/UiAuthorizationHandler.cs b/src/Readarr.Http/Authentication/UiAuthorizationHandler.cs
new file mode 100644
index 000000000..d8f08f3ea
--- /dev/null
+++ b/src/Readarr.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 Readarr.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/Readarr.Http/Authentication/UiAuthorizationPolicyProvider.cs b/src/Readarr.Http/Authentication/UiAuthorizationPolicyProvider.cs
index a5295a99f..50f1c3ada 100644
--- a/src/Readarr.Http/Authentication/UiAuthorizationPolicyProvider.cs
+++ b/src/Readarr.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());
}