Added POC of basic auth middleware
This commit is contained in:
26
src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs
Normal file
26
src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Typed configuration options for security 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; } = new string[0];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -49,6 +50,7 @@ namespace Umbraco.Extensions
|
||||
builder.Services.ConfigureOptions<ConfigureBackOfficeCookieOptions>();
|
||||
|
||||
builder.Services.AddSingleton<BackOfficeExternalLoginProviderErrorMiddleware>();
|
||||
builder.Services.AddScoped<BasicAuthAuthenticationMiddleware>();
|
||||
|
||||
builder.Services.AddUnique<IBackOfficeAntiforgery, BackOfficeAntiforgery>();
|
||||
builder.Services.AddUnique<IPasswordChanger<BackOfficeIdentityUser>, PasswordChanger<BackOfficeIdentityUser>>();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Umbraco.Extensions
|
||||
a => a.UseMiddleware<KeepAliveMiddleware>());
|
||||
|
||||
builder.AppBuilder.UseMiddleware<BackOfficeExternalLoginProviderErrorMiddleware>();
|
||||
builder.AppBuilder.UseMiddleware<BasicAuthAuthenticationMiddleware>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the
|
||||
/// principal alongside any default authentication that takes place
|
||||
/// </summary>
|
||||
public class BasicAuthAuthenticationMiddleware : IMiddleware
|
||||
{
|
||||
private readonly ILogger<BasicAuthAuthenticationMiddleware> _logger;
|
||||
private readonly IOptionsSnapshot<BasicAuthSettings> _basicAuthSettings;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
public BasicAuthAuthenticationMiddleware(
|
||||
ILogger<BasicAuthAuthenticationMiddleware> logger,
|
||||
IOptionsSnapshot<BasicAuthSettings> basicAuthSettings,
|
||||
IRuntimeState runtimeState)
|
||||
{
|
||||
_logger = logger;
|
||||
_basicAuthSettings = basicAuthSettings;
|
||||
_runtimeState = runtimeState;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
var options = _basicAuthSettings.Value;
|
||||
if (!options.Enabled || _runtimeState.Level < RuntimeLevel.Run)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var clientIPAddress = context.Connection.RemoteIpAddress;
|
||||
if (IsIpAllowListed(clientIPAddress, options.AllowedIPs))
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticateResult authenticateResult = await context.AuthenticateBackOfficeAsync();
|
||||
if (authenticateResult.Succeeded)
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
string authHeader = context.Request.Headers["Authorization"];
|
||||
if (authHeader != null && authHeader.StartsWith("Basic"))
|
||||
{
|
||||
//Extract credentials
|
||||
var encodedUsernamePassword = authHeader.Substring(6).Trim();
|
||||
var encoding = Encoding.UTF8;
|
||||
var usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
|
||||
|
||||
var seperatorIndex = usernamePassword.IndexOf(':');
|
||||
|
||||
var username = usernamePassword.Substring(0, seperatorIndex);
|
||||
var password = usernamePassword.Substring(seperatorIndex + 1);
|
||||
|
||||
|
||||
IBackOfficeSignInManager backOfficeSignInManager =
|
||||
context.RequestServices.GetRequiredService<IBackOfficeSignInManager>();
|
||||
|
||||
|
||||
SignInResult signInResult =
|
||||
await backOfficeSignInManager.PasswordSignInAsync(username, password, false, true);
|
||||
|
||||
if (signInResult.Succeeded)
|
||||
{
|
||||
await next.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUnauthorizedHeader(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no authorization header
|
||||
SetUnauthorizedHeader(context);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
context.Response.StatusCode = 401;
|
||||
context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"Umbraco as a Service login\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 IOptionsMonitor<BasicAuthSettings> _basicAuthSettingsMonitor;
|
||||
|
||||
/// <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,
|
||||
IOptionsMonitor<BasicAuthSettings> basicAuthSettings)
|
||||
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthSettings)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,12 +46,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IEnumerable<string> explicitPaths,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IOptionsMonitor<BasicAuthSettings> basicAuthSettingsMonitor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_basicAuthSettingsMonitor = basicAuthSettingsMonitor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,6 +94,11 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_basicAuthSettingsMonitor.CurrentValue.Enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IOptionsMonitor<BasicAuthSettings> _optionsSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> class.
|
||||
@@ -59,7 +60,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
ISystemClock systemClock,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IOptionsMonitor<BasicAuthSettings> optionsSnapshot)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
@@ -72,6 +74,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
_ipResolver = ipResolver;
|
||||
_systemClock = systemClock;
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_optionsSnapshot = optionsSnapshot;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -115,7 +118,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
options.CookieManager = new BackOfficeCookieManager(
|
||||
_umbracoContextAccessor,
|
||||
_runtimeState,
|
||||
_umbracoRequestPaths);
|
||||
_umbracoRequestPaths,
|
||||
_optionsSnapshot
|
||||
);
|
||||
|
||||
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,5 +1,7 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
@@ -7,6 +9,22 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
|
||||
|
||||
/// <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>
|
||||
|
||||
Reference in New Issue
Block a user