wip starts migrating get remaining seconds, removes a bunch of code from netframework thats already been moved.

This commit is contained in:
Shannon
2020-06-03 17:47:32 +10:00
parent d024443b23
commit 6004a0fb44
12 changed files with 94 additions and 463 deletions

View File

@@ -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>(),

View File

@@ -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>

View File

@@ -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);

View File

@@ -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>

View File

@@ -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

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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);
}