diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs
index 616d641751..4045421eb1 100644
--- a/src/JsonSchema/AppSettings.cs
+++ b/src/JsonSchema/AppSettings.cs
@@ -46,6 +46,7 @@ namespace JsonSchema
public UnattendedSettings Unattended { get; set; }
public RichTextEditorSettings RichTextEditor { get; set; }
public RuntimeMinificationSettings RuntimeMinification { get; set; }
+ public BasicAuthSettings BasicAuth { get; set; }
}
///
diff --git a/src/Umbraco.Core/Services/BasicAuthService.cs b/src/Umbraco.Core/Services/BasicAuthService.cs
new file mode 100644
index 0000000000..99a6f930c5
--- /dev/null
+++ b/src/Umbraco.Core/Services/BasicAuthService.cs
@@ -0,0 +1,33 @@
+using System.Net;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Core.Services
+{
+ public class BasicAuthService : IBasicAuthService
+ {
+ private BasicAuthSettings _basicAuthSettings;
+
+ public BasicAuthService(IOptionsMonitor optionsMonitor)
+ {
+ _basicAuthSettings = optionsMonitor.CurrentValue;
+
+ optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
+ }
+
+ public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled;
+
+ public bool IsIpAllowListed(IPAddress clientIpAddress)
+ {
+ foreach (var allowedIpString in _basicAuthSettings.AllowedIPs)
+ {
+ if(IPAddress.TryParse(allowedIpString, out var allowedIp) && clientIpAddress.Equals(allowedIp))
+ {
+ return true;
+ };
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Services/IBasicAuthService.cs b/src/Umbraco.Core/Services/IBasicAuthService.cs
new file mode 100644
index 0000000000..84173a629a
--- /dev/null
+++ b/src/Umbraco.Core/Services/IBasicAuthService.cs
@@ -0,0 +1,10 @@
+using System.Net;
+
+namespace Umbraco.Cms.Core.Services
+{
+ public interface IBasicAuthService
+ {
+ bool IsBasicAuthEnabled();
+ bool IsIpAllowListed(IPAddress clientIpAddress);
+ }
+}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
index 8b34289c9c..e535b399e4 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
@@ -40,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs
new file mode 100644
index 0000000000..f406acc03d
--- /dev/null
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/BasicAuthServiceTests.cs
@@ -0,0 +1,35 @@
+using System.Linq;
+using System.Net;
+using Microsoft.Extensions.Options;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services
+{
+ [TestFixture]
+ public class BasicAuthServiceTests
+ {
+ [TestCase(true, ExpectedResult = true)]
+ [TestCase(false, ExpectedResult = false)]
+ public bool IsBasicAuthEnabled(bool enabled)
+ {
+ var sut = new BasicAuthService(Mock.Of>(_ => _.CurrentValue == new BasicAuthSettings() {Enabled = enabled}));
+
+ return sut.IsBasicAuthEnabled();
+ }
+
+ [TestCase("::1", "1.1.1.1", ExpectedResult = false)]
+ [TestCase("::1", "1.1.1.1, ::1", ExpectedResult = true)]
+ [TestCase("127.0.0.1", "127.0.0.1, ::1", ExpectedResult = true)]
+ [TestCase("127.0.0.1", "", ExpectedResult = false)]
+ public bool IsBasicAuthEnabled(string clientIpAddress, string commaSeperatedAllowlist)
+ {
+ var allowedIPs = commaSeperatedAllowlist.Split(",").Select(x=>x.Trim()).ToArray();
+ var sut = new BasicAuthService(Mock.Of>(_ => _.CurrentValue == new BasicAuthSettings() {AllowedIPs = allowedIPs}));
+
+ return sut.IsIpAllowListed(IPAddress.Parse(clientIpAddress));
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs
index 3c04fe5f40..9754c2b0ab 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs
@@ -30,7 +30,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
var mgr = new BackOfficeCookieManager(
Mock.Of(),
runtime,
- new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()));
+ new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()),
+ Mock.Of());
var result = mgr.ShouldAuthenticateRequest("/umbraco");
@@ -48,7 +49,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
- Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")));
+ Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")),
+ Mock.Of());
var result = mgr.ShouldAuthenticateRequest("/umbraco");
@@ -69,7 +71,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
- Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
+ Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
+ Mock.Of());
var result = mgr.ShouldAuthenticateRequest(remainingTimeoutSecondsPath);
Assert.IsTrue(result);
@@ -90,7 +93,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
- Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
+ Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
+ Mock.Of());
var result = mgr.ShouldAuthenticateRequest("/notbackoffice");
Assert.IsFalse(result);
diff --git a/src/Umbraco.Web.BackOffice/Middleware/BasicAuthAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Middleware/BasicAuthAuthenticationMiddleware.cs
index a036858929..7027285bf3 100644
--- a/src/Umbraco.Web.BackOffice/Middleware/BasicAuthAuthenticationMiddleware.cs
+++ b/src/Umbraco.Web.BackOffice/Middleware/BasicAuthAuthenticationMiddleware.cs
@@ -1,15 +1,11 @@
using System;
-using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Extensions;
@@ -22,32 +18,28 @@ namespace Umbraco.Cms.Web.Common.Middleware
///
public class BasicAuthAuthenticationMiddleware : IMiddleware
{
- private readonly ILogger _logger;
- private readonly IOptionsSnapshot _basicAuthSettings;
private readonly IRuntimeState _runtimeState;
+ private readonly IBasicAuthService _basicAuthService;
public BasicAuthAuthenticationMiddleware(
- ILogger logger,
- IOptionsSnapshot basicAuthSettings,
- IRuntimeState runtimeState)
+ IRuntimeState runtimeState,
+ IBasicAuthService basicAuthService)
{
- _logger = logger;
- _basicAuthSettings = basicAuthSettings;
_runtimeState = runtimeState;
+ _basicAuthService = basicAuthService;
}
///
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
- var options = _basicAuthSettings.Value;
- if (!options.Enabled || _runtimeState.Level < RuntimeLevel.Run)
+ if (_runtimeState.Level < RuntimeLevel.Run || !_basicAuthService.IsBasicAuthEnabled())
{
await next(context);
return;
}
var clientIPAddress = context.Connection.RemoteIpAddress;
- if (IsIpAllowListed(clientIPAddress, options.AllowedIPs))
+ if (_basicAuthService.IsIpAllowListed(clientIPAddress))
{
await next(context);
return;
@@ -98,18 +90,7 @@ namespace Umbraco.Cms.Web.Common.Middleware
}
}
- private bool IsIpAllowListed(IPAddress clientIpAddress, string[] allowlist)
- {
- foreach (var allowedIpString in allowlist)
- {
- if(IPAddress.TryParse(allowedIpString, out var allowedIp) && clientIpAddress.Equals(allowedIp))
- {
- return true;
- };
- }
- return false;
- }
private static void SetUnauthorizedHeader(HttpContext context)
{
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs
index 5ba2fff613..5d50981f6a 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs
@@ -25,7 +25,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
private readonly IRuntimeState _runtime;
private readonly string[] _explicitPaths;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
- private readonly IOptionsMonitor _basicAuthSettingsMonitor;
+ private readonly IBasicAuthService _basicAuthService;
///
/// Initializes a new instance of the class.
@@ -34,8 +34,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IUmbracoContextAccessor umbracoContextAccessor,
IRuntimeState runtime,
UmbracoRequestPaths umbracoRequestPaths,
- IOptionsMonitor basicAuthSettings)
- : this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthSettings)
+ IBasicAuthService basicAuthService)
+ : this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthService)
{
}
@@ -47,13 +47,13 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IRuntimeState runtime,
IEnumerable explicitPaths,
UmbracoRequestPaths umbracoRequestPaths,
- IOptionsMonitor basicAuthSettingsMonitor)
+ IBasicAuthService basicAuthService)
{
_umbracoContextAccessor = umbracoContextAccessor;
_runtime = runtime;
_explicitPaths = explicitPaths?.ToArray();
_umbracoRequestPaths = umbracoRequestPaths;
- _basicAuthSettingsMonitor = basicAuthSettingsMonitor;
+ _basicAuthService = basicAuthService;
}
///
@@ -94,7 +94,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
return true;
}
- if (_basicAuthSettingsMonitor.CurrentValue.Enabled)
+ if (_basicAuthService.IsBasicAuthEnabled())
{
return true;
}
diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs
index 2d87735cab..1457732c53 100644
--- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs
+++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs
@@ -34,6 +34,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
private readonly IIpResolver _ipResolver;
private readonly ISystemClock _systemClock;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
+ private readonly IBasicAuthService _basicAuthService;
private readonly IOptionsMonitor _optionsSnapshot;
///
@@ -61,7 +62,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IIpResolver ipResolver,
ISystemClock systemClock,
UmbracoRequestPaths umbracoRequestPaths,
- IOptionsMonitor optionsSnapshot)
+ IBasicAuthService basicAuthService)
{
_serviceProvider = serviceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
@@ -74,7 +75,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
_ipResolver = ipResolver;
_systemClock = systemClock;
_umbracoRequestPaths = umbracoRequestPaths;
- _optionsSnapshot = optionsSnapshot;
+ _basicAuthService = basicAuthService;
}
///
@@ -119,7 +120,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
_umbracoContextAccessor,
_runtimeState,
_umbracoRequestPaths,
- _optionsSnapshot
+ _basicAuthService
);
options.Events = new CookieAuthenticationEvents