wip starts migrating get remaining seconds, removes a bunch of code from netframework thats already been moved.
This commit is contained in:
@@ -1,36 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Moq;
|
||||
|
||||
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Tests.Common;
|
||||
using Umbraco.Tests.Integration.Implementations;
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(WithApplication = true)]
|
||||
public class BackOfficeCookieManagerTests : UmbracoTestBase
|
||||
public class BackOfficeCookieManagerTests
|
||||
{
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_When_Not_Configured()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
//should force app ctx to show not-configured
|
||||
ConfigurationManager.AppSettings.Set(Constants.AppSettings.ConfigurationStatus, "");
|
||||
|
||||
var httpContextAccessor = TestHelper.GetHttpContextAccessor();
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GetDefaultGlobalSettings();
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
@@ -7,10 +7,12 @@ using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
@@ -36,6 +38,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IUserService _userService;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
|
||||
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
|
||||
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
|
||||
@@ -46,7 +50,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
BackOfficeSignInManager signInManager,
|
||||
IUserService userService,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IGlobalSettings globalSettings)
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger logger, IIpResolver ipResolver)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_userManager = backOfficeUserManager;
|
||||
@@ -54,6 +59,27 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_userService = userService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_ipResolver = ipResolver;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public double GetRemainingTimeoutSeconds()
|
||||
{
|
||||
var backOfficeIdentity = HttpContext.User.GetUmbracoIdentity();
|
||||
var remainingSeconds = HttpContext.User.GetRemainingAuthSeconds();
|
||||
if (remainingSeconds <= 30 && backOfficeIdentity != null)
|
||||
{
|
||||
//NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in
|
||||
// the timeout process.
|
||||
|
||||
_logger.Info<AuthenticationController>(
|
||||
"User logged will be logged out due to timeout: {Username}, IP Address: {IPAddress}",
|
||||
backOfficeIdentity.Name,
|
||||
_ipResolver.GetCurrentRequestIpAddress());
|
||||
}
|
||||
|
||||
return remainingSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
services.AddAntiforgery();
|
||||
|
||||
// TODO: We had this check in v8 where we don't enable these unless we can run...
|
||||
//if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
services
|
||||
.AddAuthentication(Constants.Security.BackOfficeAuthenticationType)
|
||||
.AddCookie(Constants.Security.BackOfficeAuthenticationType);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
@@ -30,11 +33,24 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly string _getRemainingSecondsPath;
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null)
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IGlobalSettings globalSettings,
|
||||
IRequestCache requestCache,
|
||||
LinkGenerator linkGenerator)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, linkGenerator, null)
|
||||
{ }
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache, IEnumerable<string> explicitPaths)
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IGlobalSettings globalSettings,
|
||||
IRequestCache requestCache,
|
||||
LinkGenerator linkGenerator,
|
||||
IEnumerable<string> explicitPaths)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
@@ -42,9 +58,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_globalSettings = globalSettings;
|
||||
_requestCache = requestCache;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
|
||||
// TODO: We shouldn't hard code this path
|
||||
_getRemainingSecondsPath = $"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds";
|
||||
_getRemainingSecondsPath = linkGenerator.GetUmbracoApiService<AuthenticationController>(x => x.GetRemainingTimeoutSeconds());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
@@ -36,6 +37,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
private readonly IUserService _userService;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly BackOfficeSessionIdValidator _sessionIdValidator;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public ConfigureBackOfficeCookieOptions(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
@@ -47,7 +49,8 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
IRequestCache requestCache,
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
BackOfficeSessionIdValidator sessionIdValidator)
|
||||
BackOfficeSessionIdValidator sessionIdValidator,
|
||||
LinkGenerator linkGenerator)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_securitySettings = securitySettings;
|
||||
@@ -59,6 +62,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_userService = userService;
|
||||
_ipResolver = ipResolver;
|
||||
_sessionIdValidator = sessionIdValidator;
|
||||
_linkGenerator = linkGenerator;
|
||||
}
|
||||
|
||||
public void Configure(string name, CookieAuthenticationOptions options)
|
||||
@@ -98,7 +102,8 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_runtimeState,
|
||||
_hostingEnvironment,
|
||||
_globalSettings,
|
||||
_requestCache);
|
||||
_requestCache,
|
||||
_linkGenerator);
|
||||
// _explicitPaths); TODO: Implement this once we do OAuth somehow
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Web.Common.Install;
|
||||
using Umbraco.Core.Hosting;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -57,6 +58,23 @@ namespace Umbraco.Extensions
|
||||
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), id);
|
||||
}
|
||||
|
||||
public static string GetUmbracoApiService<T>(this LinkGenerator url, Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
var method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
var methodParams = ExpressionHelper.GetMethodParams(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result ");
|
||||
}
|
||||
|
||||
if (methodParams.Any() == false)
|
||||
{
|
||||
return url.GetUmbracoApiService<T>(method.Name);
|
||||
}
|
||||
return url.GetUmbracoApiService<T>(method.Name, methodParams.Values.First());
|
||||
}
|
||||
|
||||
public static string GetUmbracoApiServiceBaseUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
|
||||
@@ -29,228 +29,6 @@ namespace Umbraco.Web.Security
|
||||
/// </summary>
|
||||
public static class AppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure Default Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
ServiceContext services,
|
||||
IGlobalSettings globalSettings,
|
||||
UmbracoMapper mapper,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeOwinUserManager>(
|
||||
(options, owinContext) => BackOfficeOwinUserManager.Create(
|
||||
services.UserService,
|
||||
services.EntityService,
|
||||
services.ExternalLoginService,
|
||||
globalSettings,
|
||||
mapper,
|
||||
passwordConfiguration,
|
||||
ipResolver,
|
||||
new BackOfficeIdentityErrorDescriber(),
|
||||
app.GetDataProtectionProvider(),
|
||||
new NullLogger<BackOfficeUserManager<BackOfficeIdentityUser>>()));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeOwinUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
//Create a sign in manager per request
|
||||
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger<BackOfficeSignInManager>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure a custom UserStore with the Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
IRuntimeState runtimeState,
|
||||
IGlobalSettings globalSettings,
|
||||
BackOfficeUserStore customUserStore,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
if (customUserStore == null) throw new ArgumentNullException(nameof(customUserStore));
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeOwinUserManager>(
|
||||
(options, owinContext) => BackOfficeOwinUserManager.Create(
|
||||
passwordConfiguration,
|
||||
ipResolver,
|
||||
customUserStore,
|
||||
new BackOfficeIdentityErrorDescriber(),
|
||||
app.GetDataProtectionProvider(),
|
||||
new NullLogger<BackOfficeUserManager<BackOfficeIdentityUser>>()));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeOwinUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
//Create a sign in manager per request
|
||||
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// By default this will be configured to execute on PipelineStage.Authenticate
|
||||
/// </remarks>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtimeState,
|
||||
IUserService userService,
|
||||
IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, userService, globalSettings, securitySettings, hostingEnvironment, requestCache, PipelineStage.Authenticate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="stage">
|
||||
/// Configurable pipeline stage
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtimeState,
|
||||
IUserService userService,
|
||||
IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRequestCache requestCache,
|
||||
PipelineStage stage)
|
||||
{
|
||||
//Create the default options and provider
|
||||
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
|
||||
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, hostingEnvironment, requestCache, authOptions, stage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="cookieOptions">Custom auth cookie options can be specified to have more control over the cookie authentication logic</param>
|
||||
/// <param name="stage">
|
||||
/// Configurable pipeline stage
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, CookieAuthenticationOptions cookieOptions, PipelineStage stage)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
if (cookieOptions == null) throw new ArgumentNullException(nameof(cookieOptions));
|
||||
if (cookieOptions.Provider == null)
|
||||
throw new ArgumentNullException("cookieOptions.Provider cannot be null.", nameof(cookieOptions));
|
||||
if (cookieOptions.Provider is BackOfficeCookieAuthenticationProvider == false)
|
||||
throw new ArgumentException($"cookieOptions.Provider must be of type {typeof(BackOfficeCookieAuthenticationProvider)}.", nameof(cookieOptions));
|
||||
|
||||
app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, runtimeState, requestCache, stage);
|
||||
|
||||
//don't apply if app is not ready
|
||||
if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
|
||||
var cookieAuthOptions = app.CreateUmbracoCookieAuthOptions(
|
||||
umbracoContextAccessor, globalSettings, runtimeState, securitySettings,
|
||||
//This defines the explicit path read cookies from for this middleware
|
||||
hostingEnvironment, requestCache, new[] {$"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"});
|
||||
cookieAuthOptions.Provider = cookieOptions.Provider;
|
||||
|
||||
//This is a custom middleware, we need to return the user's remaining logged in seconds
|
||||
app.Use<GetUserSecondsMiddleWare>(
|
||||
cookieAuthOptions,
|
||||
Current.Configs.Global(),
|
||||
Current.Configs.Security(),
|
||||
app.CreateLogger<GetUserSecondsMiddleWare>(),
|
||||
Current.HostingEnvironment);
|
||||
|
||||
//This is required so that we can read the auth ticket format outside of this pipeline
|
||||
app.CreatePerOwinContext<UmbracoAuthTicketDataProtector>(
|
||||
(options, context) => new UmbracoAuthTicketDataProtector(cookieOptions.TicketDataFormat));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static bool _markerSet = false;
|
||||
|
||||
/// <summary>
|
||||
/// This registers the exact type of the user manager in owin so we can extract it
|
||||
/// when required in order to extract the user manager instance
|
||||
/// </summary>
|
||||
/// <typeparam name="TManager"></typeparam>
|
||||
/// <typeparam name="TUser"></typeparam>
|
||||
/// <param name="app"></param>
|
||||
/// <remarks>
|
||||
/// This is required because a developer can specify a custom user manager and due to generic types the key name will registered
|
||||
/// differently in the owin context
|
||||
/// </remarks>
|
||||
private static void SetBackOfficeUserManagerType<TManager, TUser>(this IAppBuilder app)
|
||||
where TManager : BackOfficeUserManager<TUser>
|
||||
where TUser : BackOfficeIdentityUser
|
||||
{
|
||||
if (_markerSet) throw new InvalidOperationException("The back office user manager marker has already been set, only one back office user manager can be configured");
|
||||
|
||||
//on each request set the user manager getter -
|
||||
// this is required purely because Microsoft.Owin.IOwinContext is super inflexible with it's Get since it can only be
|
||||
// a generic strongly typed instance
|
||||
app.Use((context, func) =>
|
||||
{
|
||||
context.Set(BackOfficeOwinUserManager.OwinMarkerKey, new BackOfficeUserManagerMarker<TManager, TUser>());
|
||||
return func();
|
||||
});
|
||||
}
|
||||
|
||||
private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, IRuntimeState runtimeState, IRequestCache requestCache, PipelineStage stage)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
|
||||
//First the normal cookie middleware
|
||||
app.Use(typeof(CookieAuthenticationMiddleware), app, options);
|
||||
//don't apply if app is not ready
|
||||
if (runtimeState.Level == RuntimeLevel.Upgrade || runtimeState.Level == RuntimeLevel.Run)
|
||||
{
|
||||
//Then our custom middlewares
|
||||
app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, Current.UmbracoContextAccessor, requestCache);
|
||||
app.Use(typeof(FixWindowsAuthMiddlware));
|
||||
}
|
||||
|
||||
//Marks all of the above middlewares to execute on Authenticate
|
||||
app.UseStageMarker(stage);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct
|
||||
@@ -297,8 +75,6 @@ namespace Umbraco.Web.Security
|
||||
AuthenticationMode = AuthenticationMode.Passive,
|
||||
CookieName = Constants.Security.BackOfficeExternalCookieName,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(5),
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, hostingEnvironment, globalSettings, requestCache),
|
||||
CookiePath = "/",
|
||||
CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest,
|
||||
CookieHttpOnly = true,
|
||||
@@ -353,8 +129,8 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
if (runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
app.Use(typeof(PreviewAuthenticationMiddleware), authOptions, globalSettings, hostingEnvironment);
|
||||
//var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
app.Use(typeof(PreviewAuthenticationMiddleware), /*authOptions*/null, globalSettings, hostingEnvironment);
|
||||
|
||||
// This middleware must execute at least on PostAuthentication, by default it is on Authorize
|
||||
// The middleware needs to execute after the RoleManagerModule executes which is during PostAuthenticate,
|
||||
@@ -372,40 +148,6 @@ namespace Umbraco.Web.Security
|
||||
Thread.CurrentThread.SanitizeThreadCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the default umb cookie auth options
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="explicitPaths"></param>
|
||||
/// <returns></returns>
|
||||
public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IGlobalSettings globalSettings, IRuntimeState runtimeState, ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, string[] explicitPaths = null)
|
||||
{
|
||||
//this is how aspnet wires up the default AuthenticationTicket protector so we'll use the same code
|
||||
var ticketDataFormat = new TicketDataFormat(
|
||||
app.CreateDataProtector(typeof (CookieAuthenticationMiddleware).FullName,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
"v1"));
|
||||
|
||||
var authOptions = new UmbracoBackOfficeCookieAuthOptions(
|
||||
explicitPaths,
|
||||
umbracoContextAccessor,
|
||||
securitySettings,
|
||||
globalSettings,
|
||||
hostingEnvironment,
|
||||
runtimeState,
|
||||
ticketDataFormat,
|
||||
requestCache);
|
||||
|
||||
return authOptions;
|
||||
}
|
||||
public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app, Func<T> createCallback)
|
||||
where T : class, IDisposable
|
||||
{
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom cookie manager that is used to read the cookie from the request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install and /base therefore we cannot just set the cookie path to be /umbraco,
|
||||
/// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path.
|
||||
/// </remarks>
|
||||
internal class BackOfficeCookieManager : ChunkingCookieManager, Microsoft.Owin.Infrastructure.ICookieManager
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly string _getRemainingSecondsPath;
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null)
|
||||
{ }
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache, IEnumerable<string> explicitPaths)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_globalSettings = globalSettings;
|
||||
_requestCache = requestCache;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
|
||||
_getRemainingSecondsPath = $"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly implement this so that we filter the request
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
string Microsoft.Owin.Infrastructure.ICookieManager.GetRequestCookie(IOwinContext context, string key)
|
||||
{
|
||||
if (_umbracoContextAccessor.UmbracoContext == null || context.Request.Uri.IsClientSideRequest())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ShouldAuthenticateRequest(
|
||||
context,
|
||||
_umbracoContextAccessor.UmbracoContext.OriginalRequestUrl) == false
|
||||
//Don't auth request, don't return a cookie
|
||||
? null
|
||||
//Return the default implementation
|
||||
: GetRequestCookie(context, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we should authenticate the request
|
||||
/// </summary>
|
||||
/// <param name="owinContext"></param>
|
||||
/// <param name="originalRequestUrl"></param>
|
||||
/// <param name="checkForceAuthTokens"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// We auth the request when:
|
||||
/// * it is a back office request
|
||||
/// * it is an installer request
|
||||
/// * it is a /base request
|
||||
/// * it is a preview request
|
||||
/// </remarks>
|
||||
internal bool ShouldAuthenticateRequest(IOwinContext owinContext, Uri originalRequestUrl, bool checkForceAuthTokens = true)
|
||||
{
|
||||
// Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need
|
||||
// to know a current user in this scenario - we treat it as a new install. Without this we can have some issues
|
||||
// when people have older invalid cookies on the same domain since our user managers might attempt to lookup a user
|
||||
// and we don't even have a db.
|
||||
// was: app.IsConfigured == false (equiv to !Run) && dbContext.IsDbConfigured == false (equiv to Install)
|
||||
// so, we handle .Install here and NOT .Upgrade
|
||||
if (_runtime.Level == RuntimeLevel.Install)
|
||||
return false;
|
||||
|
||||
var request = owinContext.Request;
|
||||
//check the explicit paths
|
||||
if (_explicitPaths != null)
|
||||
{
|
||||
return _explicitPaths.Any(x => x.InvariantEquals(request.Uri.AbsolutePath));
|
||||
}
|
||||
|
||||
//check user seconds path
|
||||
if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false;
|
||||
|
||||
if (//check the explicit flag
|
||||
(checkForceAuthTokens && owinContext.Get<bool?>(Constants.Security.ForceReAuthFlag) != null)
|
||||
|| (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null)
|
||||
//check back office
|
||||
|| request.Uri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment)
|
||||
//check installer
|
||||
|| request.Uri.IsInstallerRequest(_hostingEnvironment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,19 +57,19 @@ namespace Umbraco.Web.Security
|
||||
protected override Task ApplyResponseGrantAsync()
|
||||
{
|
||||
if (_umbracoContextAccessor.UmbracoContext == null || Context.Request.Uri.IsClientSideRequest())
|
||||
{
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
//Now we need to check if we should force renew this based on a flag in the context and whether this is a request that is not normally renewed by OWIN...
|
||||
// which means that it is not a normal URL that is authenticated.
|
||||
|
||||
var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager)
|
||||
.ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl,
|
||||
//Pass in false, we want to know if this is a normal auth'd page
|
||||
checkForceAuthTokens: false);
|
||||
//This is auth'd normally, so OWIN will naturally take care of the cookie renewal
|
||||
if (normalAuthUrl) return Task.FromResult(0);
|
||||
//var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager)
|
||||
// .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl,
|
||||
// //Pass in false, we want to know if this is a normal auth'd page
|
||||
// checkForceAuthTokens: false);
|
||||
////This is auth'd normally, so OWIN will naturally take care of the cookie renewal
|
||||
//if (normalAuthUrl) return Task.FromResult(0);
|
||||
|
||||
//check for the special flag in either the owin or http context
|
||||
var shouldRenew = Context.Get<bool?>(Constants.Security.ForceReAuthFlag) != null || (_requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null);
|
||||
|
||||
@@ -18,34 +18,6 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
public int LoginTimeoutMinutes { get; }
|
||||
|
||||
public UmbracoBackOfficeCookieAuthOptions(
|
||||
string[] explicitPaths,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISecuritySettings securitySettings,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
ISecureDataFormat<AuthenticationTicket> secureDataFormat,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
var secureDataFormat1 = secureDataFormat ?? throw new ArgumentNullException(nameof(secureDataFormat));
|
||||
LoginTimeoutMinutes = globalSettings.TimeOutInMinutes;
|
||||
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
|
||||
|
||||
SlidingExpiration = true;
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(LoginTimeoutMinutes);
|
||||
CookieDomain = securitySettings.AuthCookieDomain;
|
||||
CookieName = securitySettings.AuthCookieName;
|
||||
CookieHttpOnly = true;
|
||||
CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest;
|
||||
CookiePath = "/";
|
||||
|
||||
TicketDataFormat = new UmbracoSecureDataFormat(LoginTimeoutMinutes, secureDataFormat1);
|
||||
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, hostingEnvironment, globalSettings, requestCache, explicitPaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cookie options for saving the auth cookie
|
||||
/// </summary>
|
||||
|
||||
@@ -339,7 +339,6 @@
|
||||
<Compile Include="Security\AppBuilderExtensions.cs" />
|
||||
<Compile Include="Security\AuthenticationOptionsExtensions.cs" />
|
||||
<Compile Include="Security\AuthenticationManagerExtensions.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieManager.cs" />
|
||||
<Compile Include="Security\UmbracoBackOfficeCookieAuthOptions.cs" />
|
||||
<Compile Include="Trees\DataTypeTreeController.cs" />
|
||||
<Compile Include="Trees\FileSystemTreeController.cs" />
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace Umbraco.Web
|
||||
protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services)
|
||||
{
|
||||
app.SetUmbracoLoggerFactory();
|
||||
ConfigureUmbracoUserManager(app);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,21 +80,6 @@ namespace Umbraco.Web
|
||||
.FinalizeMiddlewareConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the Identity user manager for use with Umbraco Back office
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
protected virtual void ConfigureUmbracoUserManager(IAppBuilder app)
|
||||
{
|
||||
// (EXPERT: an overload accepts a custom BackOfficeUserStore implementation)
|
||||
app.ConfigureUserManagerForUmbracoBackOffice(
|
||||
Services,
|
||||
GlobalSettings,
|
||||
Mapper,
|
||||
UserPasswordConfig,
|
||||
IpResolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure external/OAuth login providers
|
||||
/// </summary>
|
||||
@@ -105,7 +89,8 @@ namespace Umbraco.Web
|
||||
// Ensure owin is configured for Umbraco back office authentication.
|
||||
// Front-end OWIN cookie configuration must be declared after this code.
|
||||
app
|
||||
.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate)
|
||||
// already moved to netcore
|
||||
//.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate)
|
||||
.UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate)
|
||||
.UseUmbracoPreviewAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authorize);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user