Merge pull request #10803 from umbraco/v9/feature/basic_auth_middleware
V9: Add basic auth middleware
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
27
src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs
Normal file
27
src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Typed configuration options for basic authentication settings.
|
||||
/// </summary>
|
||||
[UmbracoOptions(Constants.Configuration.ConfigBasicAuth)]
|
||||
public class BasicAuthSettings
|
||||
{
|
||||
private const bool StaticEnabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to keep the user logged in.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticEnabled)]
|
||||
public bool Enabled { get; set; } = StaticEnabled;
|
||||
|
||||
|
||||
public string[] AllowedIPs { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
|
||||
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
|
||||
public const string ConfigSecurity = ConfigPrefix + "Security";
|
||||
public const string ConfigBasicAuth = ConfigPrefix + "BasicAuth";
|
||||
public const string ConfigTours = ConfigPrefix + "Tours";
|
||||
public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder";
|
||||
public const string ConfigWebRouting = ConfigPrefix + "WebRouting";
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
.AddUmbracoOptions<UmbracoPluginSettings>()
|
||||
.AddUmbracoOptions<UnattendedSettings>()
|
||||
.AddUmbracoOptions<RichTextEditorSettings>()
|
||||
.AddUmbracoOptions<BasicAuthSettings>()
|
||||
.AddUmbracoOptions<RuntimeMinificationSettings>();
|
||||
|
||||
return builder;
|
||||
|
||||
10
src/Umbraco.Core/Services/IBasicAuthService.cs
Normal file
10
src/Umbraco.Core/Services/IBasicAuthService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services
|
||||
{
|
||||
public interface IBasicAuthService
|
||||
{
|
||||
bool IsBasicAuthEnabled();
|
||||
bool IsIpAllowListed(IPAddress clientIpAddress);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
|
||||
builder.Services.AddUnique<IDomainService, DomainService>();
|
||||
builder.Services.AddUnique<IAuditService, AuditService>();
|
||||
builder.Services.AddUnique<ICacheInstructionService, CacheInstructionService>();
|
||||
builder.Services.AddUnique<IBasicAuthService, BasicAuthService>();
|
||||
builder.Services.AddUnique<ITagService, TagService>();
|
||||
builder.Services.AddUnique<IContentService, ContentService>();
|
||||
builder.Services.AddUnique<IUserService, UserService>();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.Implement
|
||||
{
|
||||
public class BasicAuthService : IBasicAuthService
|
||||
{
|
||||
private BasicAuthSettings _basicAuthSettings;
|
||||
|
||||
public BasicAuthService(IOptionsMonitor<BasicAuthSettings> 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 (IPNetwork.TryParse(allowedIpString, out IPNetwork allowedIp) && allowedIp.Contains(clientIpAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
|
||||
<PackageReference Include="IPNetwork2" Version="2.5.329" />
|
||||
<PackageReference Include="MailKit" Version="2.13.0" />
|
||||
<PackageReference Include="Markdown" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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.Implement;
|
||||
|
||||
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<IOptionsMonitor<BasicAuthSettings>>(_ => _.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)]
|
||||
[TestCase("125.125.125.1", "125.125.125.0/24", ExpectedResult = true)]
|
||||
[TestCase("125.125.124.1", "125.125.125.0/24", ExpectedResult = false)]
|
||||
public bool IsIpAllowListed(string clientIpAddress, string commaSeperatedAllowlist)
|
||||
{
|
||||
var allowedIPs = commaSeperatedAllowlist.Split(",").Select(x=>x.Trim()).ToArray();
|
||||
var sut = new BasicAuthService(Mock.Of<IOptionsMonitor<BasicAuthSettings>>(_ => _.CurrentValue == new BasicAuthSettings() {AllowedIPs = allowedIPs}));
|
||||
|
||||
return sut.IsIpAllowListed(IPAddress.Parse(clientIpAddress));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
|
||||
var mgr = new BackOfficeCookieManager(
|
||||
Mock.Of<IUmbracoContextAccessor>(),
|
||||
runtime,
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()));
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()),
|
||||
Mock.Of<IBasicAuthService>());
|
||||
|
||||
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<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")));
|
||||
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")),
|
||||
Mock.Of<IBasicAuthService>());
|
||||
|
||||
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<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
|
||||
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
|
||||
Mock.Of<IBasicAuthService>());
|
||||
|
||||
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<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
|
||||
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
|
||||
Mock.Of<IBasicAuthService>());
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest("/notbackoffice");
|
||||
Assert.IsFalse(result);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions
|
||||
{
|
||||
[TestFixture]
|
||||
public class HttpContextExtensionTests
|
||||
{
|
||||
[Test]
|
||||
public void TryGetBasicAuthCredentials_WithoutHeader_ReturnsFalse()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
var result = httpContext.TryGetBasicAuthCredentials(out string _, out string _);
|
||||
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetBasicAuthCredentials_WithHeader_ReturnsTrueWithCredentials()
|
||||
{
|
||||
const string Username = "fred";
|
||||
const string Password = "test";
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{Username}:{Password}"));
|
||||
httpContext.Request.Headers.Add("Authorization", $"Basic {credentials}");
|
||||
|
||||
bool result = httpContext.TryGetBasicAuthCredentials(out string username, out string password);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(Username, username);
|
||||
Assert.AreEqual(Password, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Web.Common.Security;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Web.BackOffice.Authorization;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
@@ -10,5 +12,6 @@ namespace Umbraco.Extensions
|
||||
|
||||
public static BackOfficeExternalLoginProviderErrors GetExternalLoginProviderErrors(this HttpContext httpContext)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Cms.Web.BackOffice.Middleware;
|
||||
using Umbraco.Cms.Web.BackOffice.Routing;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
using Umbraco.Cms.Web.Common.Extensions;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
|
||||
@@ -2,7 +2,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
@@ -23,6 +25,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IBasicAuthService _basicAuthService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeCookieManager"/> class.
|
||||
@@ -30,8 +33,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,12 +46,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IEnumerable<string> explicitPaths,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_basicAuthService = basicAuthService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,6 +94,11 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_basicAuthService.IsBasicAuthEnabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ 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<BasicAuthSettings> _optionsSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> class.
|
||||
@@ -59,7 +61,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
ISystemClock systemClock,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
@@ -72,6 +75,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
_ipResolver = ipResolver;
|
||||
_systemClock = systemClock;
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_basicAuthService = basicAuthService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -115,7 +119,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
options.CookieManager = new BackOfficeCookieManager(
|
||||
_umbracoContextAccessor,
|
||||
_runtimeState,
|
||||
_umbracoRequestPaths);
|
||||
_umbracoRequestPaths,
|
||||
_basicAuthService
|
||||
);
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
|
||||
@@ -101,10 +101,10 @@ namespace Umbraco.Extensions
|
||||
services.AddSingleton(httpContextAccessor);
|
||||
|
||||
var requestCache = new HttpContextRequestAppCache(httpContextAccessor);
|
||||
var appCaches = AppCaches.Create(requestCache);
|
||||
var appCaches = AppCaches.Create(requestCache);
|
||||
|
||||
IProfiler profiler = GetWebProfiler(config);
|
||||
|
||||
|
||||
ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
|
||||
|
||||
TypeLoader typeLoader = services.AddTypeLoader(
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Umbraco.Extensions
|
||||
|
||||
IOptions<UmbracoPipelineOptions> startupOptions = app.ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
|
||||
app.RunPrePipeline(startupOptions.Value);
|
||||
|
||||
|
||||
app.UseUmbracoCore();
|
||||
app.UseUmbracoRequestLogging();
|
||||
|
||||
|
||||
@@ -14,13 +14,7 @@ namespace Umbraco.Extensions
|
||||
/// <returns></returns>
|
||||
public static async Task<AuthenticateResult> AuthenticateBackOfficeAsync(this ControllerBase controller)
|
||||
{
|
||||
if (controller.HttpContext == null)
|
||||
{
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
var result = await controller.HttpContext.AuthenticateAsync(Cms.Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
return result;
|
||||
return await controller.HttpContext.AuthenticateBackOfficeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,60 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to get the basic auth username and password from the http context.
|
||||
/// </summary>
|
||||
public static bool TryGetBasicAuthCredentials(this HttpContext httpContext, out string username, out string password)
|
||||
{
|
||||
username = null;
|
||||
password = null;
|
||||
|
||||
if (httpContext.Request.Headers.TryGetValue("Authorization", out StringValues authHeaders))
|
||||
{
|
||||
var authHeader = authHeaders.ToString();
|
||||
if (authHeader is not null && authHeader.StartsWith("Basic"))
|
||||
{
|
||||
// Extract credentials.
|
||||
var encodedUsernamePassword = authHeader.Substring(6).Trim();
|
||||
Encoding encoding = Encoding.UTF8;
|
||||
var usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
|
||||
|
||||
var seperatorIndex = usernamePassword.IndexOf(':');
|
||||
|
||||
username = usernamePassword.Substring(0, seperatorIndex);
|
||||
password = usernamePassword.Substring(seperatorIndex + 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the authentication process
|
||||
/// </summary>
|
||||
public static async Task<AuthenticateResult> AuthenticateBackOfficeAsync(this HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
var result = await httpContext.AuthenticateAsync(Cms.Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value in the request form or query string for the key
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Infrastructure.DependencyInjection;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
using Umbraco.Cms.Web.Common.Routing;
|
||||
using Umbraco.Cms.Web.Website.Collections;
|
||||
using Umbraco.Cms.Web.Website.Controllers;
|
||||
@@ -47,6 +48,7 @@ namespace Umbraco.Extensions
|
||||
builder.Services.AddSingleton<MemberModelBuilderFactory>();
|
||||
|
||||
builder.Services.AddSingleton<PublicAccessMiddleware>();
|
||||
builder.Services.AddSingleton<BasicAuthenticationMiddleware>();
|
||||
|
||||
builder
|
||||
.AddDistributedCache()
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
using Umbraco.Cms.Web.Website.Middleware;
|
||||
using Umbraco.Cms.Web.Website.Routing;
|
||||
|
||||
@@ -20,6 +21,7 @@ namespace Umbraco.Extensions
|
||||
public static IUmbracoMiddlewareBuilder WithWebsite(this IUmbracoMiddlewareBuilder builder)
|
||||
{
|
||||
builder.AppBuilder.UseMiddleware<PublicAccessMiddleware>();
|
||||
builder.AppBuilder.UseMiddleware<BasicAuthenticationMiddleware>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.BackOffice.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides basic authentication via back-office credentials for public website access if configured for use and the client IP is not allow listed.
|
||||
/// </summary>
|
||||
public class BasicAuthenticationMiddleware : IMiddleware
|
||||
{
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IBasicAuthService _basicAuthService;
|
||||
|
||||
public BasicAuthenticationMiddleware(
|
||||
IRuntimeState runtimeState,
|
||||
IBasicAuthService basicAuthService)
|
||||
{
|
||||
_runtimeState = runtimeState;
|
||||
_basicAuthService = basicAuthService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
if (_runtimeState.Level < RuntimeLevel.Run || context.Request.IsBackOfficeRequest() || !_basicAuthService.IsBasicAuthEnabled())
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
IPAddress clientIPAddress = context.Connection.RemoteIpAddress;
|
||||
if (_basicAuthService.IsIpAllowListed(clientIPAddress))
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticateResult authenticateResult = await context.AuthenticateBackOfficeAsync();
|
||||
if (authenticateResult.Succeeded)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.TryGetBasicAuthCredentials(out var username, out var password))
|
||||
{
|
||||
IBackOfficeSignInManager backOfficeSignInManager =
|
||||
context.RequestServices.GetService<IBackOfficeSignInManager>();
|
||||
|
||||
if (backOfficeSignInManager is not null)
|
||||
{
|
||||
SignInResult signInResult =
|
||||
await backOfficeSignInManager.PasswordSignInAsync(username, password, false, true);
|
||||
|
||||
if (signInResult.Succeeded)
|
||||
{
|
||||
await next.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUnauthorizedHeader(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUnauthorizedHeader(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no authorization header
|
||||
SetUnauthorizedHeader(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetUnauthorizedHeader(HttpContext context)
|
||||
{
|
||||
context.Response.StatusCode = 401;
|
||||
context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"Umbraco login\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user