Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/migrate-remaining-trees

This commit is contained in:
Bjarke Berg
2020-06-11 15:21:51 +02:00
59 changed files with 841 additions and 668 deletions

View File

@@ -1,13 +1,17 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Net;
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;
@@ -33,6 +37,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
@@ -43,7 +49,8 @@ namespace Umbraco.Web.BackOffice.Controllers
BackOfficeSignInManager signInManager,
IUserService userService,
UmbracoMapper umbracoMapper,
IGlobalSettings globalSettings)
IGlobalSettings globalSettings,
ILogger logger, IIpResolver ipResolver)
{
_webSecurity = webSecurity;
_userManager = backOfficeUserManager;
@@ -51,6 +58,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>
@@ -156,6 +184,22 @@ namespace Umbraco.Web.BackOffice.Controllers
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
/// <summary>
/// Logs the current user out
/// </summary>
/// <returns></returns>
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
public IActionResult PostLogout()
{
HttpContext.SignOutAsync(Core.Constants.Security.BackOfficeAuthenticationType);
_logger.Info<AuthenticationController>("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress);
_userManager.RaiseLogoutSuccessEvent(User, int.Parse(User.Identity.GetUserId()));
return Ok();
}
/// <summary>
/// Return the <see cref="UserDetail"/> for the given <see cref="IUser"/>
/// </summary>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
@@ -11,7 +12,7 @@ using Umbraco.Web.Common.Attributes;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class BackOfficeAssetsController : UmbracoAuthorizedJsonController
{
private readonly IFileSystem _jsLibFileSystem;

View File

@@ -29,7 +29,7 @@ namespace Umbraco.Web.BackOffice.Controllers
{
// TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException
// ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
//[PrefixlessBodyModelValidator]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)]
public class CodeFileController : BackOfficeNotificationsController

View File

@@ -25,7 +25,7 @@ using Umbraco.Web.WebApi.Filters;
namespace Umbraco.Web.BackOffice.Controllers
{
//we need to fire up the controller like this to enable loading of remote css directly from this controller
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[ValidationFilter]
[AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions
[IsBackOffice]

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// The security for this controller is defined to allow full CRUD access to data types if the user has access to either:
/// Content Types, Member Types or Media Types ... and of course to Data Types
/// </remarks>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoTreeAuthorizeAttribute(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)]
public class DataTypeController : BackOfficeNotificationsController
{

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// The security for this controller is defined to allow full CRUD access to dictionary if the user has access to either:
/// Dictionary
/// </remarks>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoTreeAuthorize(Constants.Trees.Dictionary)]
public class DictionaryController : BackOfficeNotificationsController
{

View File

@@ -18,7 +18,7 @@ using SearchResult = Umbraco.Web.Models.ContentEditing.SearchResult;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class ExamineManagementController : UmbracoAuthorizedJsonController
{
private readonly IExamineManager _examineManager;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Media;
using Umbraco.Core.Models;
using Umbraco.Web.Common.Attributes;
@@ -22,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// building to generate correct URLs
/// </para>
/// </remarks>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController
{
private readonly IImageUrlGenerator _imageUrlGenerator;

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Media;
@@ -13,7 +14,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// A controller used to return images for media
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class ImagesController : UmbracoAuthorizedApiController
{
private readonly IMediaFileSystem _mediaFileSystem;

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// Backoffice controller supporting the dashboard for language administration.
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
//[PrefixlessBodyModelValidator]
public class LanguageController : UmbracoAuthorizedJsonController
{

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// The API controller used for getting log history
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class LogController : UmbracoAuthorizedJsonController
{
private readonly IMediaFileSystem _mediaFileSystem;

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class LogViewerController : UmbracoAuthorizedJsonController
{
private readonly ILogViewer _logViewer;

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// A controller used for managing packages in the back office
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)]
public class PackageController : UmbracoAuthorizedJsonController
{

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// A controller used for installing packages and managing all of the data in the packages section in the back office
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)]
public class PackageInstallController : UmbracoAuthorizedJsonController
{

View File

@@ -15,7 +15,7 @@ using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class RedirectUrlManagementController : UmbracoAuthorizedApiController
{
private readonly ILogger _logger;

View File

@@ -17,7 +17,7 @@ using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)]
public class RelationController : UmbracoAuthorizedJsonController
{

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// The API controller for editing relation types.
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoTreeAuthorizeAttribute(Constants.Trees.RelationTypes)]
public class RelationTypeController : BackOfficeNotificationsController
{

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Umbraco.Core;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
@@ -17,7 +18,7 @@ namespace Umbraco.Web.Editors
/// <summary>
/// The API controller used for using the list of sections
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class SectionController : UmbracoAuthorizedJsonController
{
private readonly IControllerFactory _controllerFactory;

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <summary>
/// The API controller used for retrieving available stylesheets
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class StylesheetController : UmbracoAuthorizedJsonController
{
private readonly IFileService _fileService;

View File

@@ -16,7 +16,7 @@ using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoTreeAuthorizeAttribute(Constants.Trees.Templates)]
public class TemplateController : BackOfficeNotificationsController
{

View File

@@ -19,7 +19,7 @@ using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[UmbracoApplicationAuthorize(
Constants.Applications.Content,
Constants.Applications.Media,

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
using Umbraco.Core.Services;
@@ -13,7 +14,7 @@ using Umbraco.Web.Tour;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class TourController : UmbracoAuthorizedJsonController
{
private readonly TourFilterCollection _filters;

View File

@@ -15,7 +15,7 @@ using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class UpdateCheckController : UmbracoAuthorizedJsonController
{
private readonly IUpgradeService _upgradeService;

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

@@ -24,6 +24,8 @@ namespace Umbraco.Web.BackOffice.Filters
/// </remarks>
public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute
{
// TODO: Either make this inherit from TypeFilter or make this just a normal IActionFilter
private readonly ILogger _logger;
private readonly IBackOfficeAntiforgery _antiforgery;
private readonly ICookieManager _cookieManager;

View File

@@ -4,10 +4,11 @@ using Microsoft.AspNetCore.Mvc;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Core;
namespace Umbraco.Web.BackOffice.PropertyEditors
{
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class NestedContentController : UmbracoAuthorizedJsonController
{
private readonly IContentTypeService _contentTypeService;

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Web.BackOffice.PropertyEditors
/// <summary>
/// ApiController to provide RTE configuration with available plugins and commands from the RTE config
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class RichTextPreValueController : UmbracoAuthorizedJsonController
{
private readonly IHostingEnvironment _hostingEnvironment;

View File

@@ -5,13 +5,14 @@ using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Core.Media;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Media.EmbedProviders;
using Umbraco.Core;
namespace Umbraco.Web.BackOffice.PropertyEditors
{
/// <summary>
/// A controller used for the embed dialog
/// </summary>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class RteEmbedController : UmbracoAuthorizedJsonController
{
private readonly EmbedProvidersCollection _embedCollection;

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Web.BackOffice.PropertyEditors
/// DO NOT inherit from UmbracoAuthorizedJsonController since we don't want to use the angularized
/// json formatter as it causes problems.
/// </remarks>
[PluginController("UmbracoApi")]
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
public class TagsDataController : UmbracoAuthorizedApiController
{
private readonly ITagQuery _tagQuery;

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
{
@@ -20,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Security
/// 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, ICookieManager
public class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IRuntimeState _runtime;
@@ -28,13 +31,25 @@ namespace Umbraco.Web.BackOffice.Security
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,
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 +57,6 @@ 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";
}
/// <summary>
@@ -60,7 +72,7 @@ namespace Umbraco.Web.BackOffice.Security
/// * it is a /base request
/// * it is a preview request
/// </remarks>
internal bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true)
public bool ShouldAuthenticateRequest(Uri requestUri, 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
@@ -75,9 +87,6 @@ namespace Umbraco.Web.BackOffice.Security
if (_explicitPaths != null)
return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath));
//check user seconds path
if (requestUri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false;
if (//check the explicit flag
checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null
//check back office

View File

@@ -9,7 +9,6 @@ namespace Umbraco.Web.BackOffice.Security
/// <summary>
/// Custom secure format that ensures the Identity in the ticket is <see cref="UmbracoBackOfficeIdentity"/> and not just a ClaimsIdentity
/// </summary>
// TODO: Unsure if we really need this, there's no real reason why we have a custom Identity instead of just a ClaimsIdentity
internal class BackOfficeSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly int _loginTimeoutMinutes;
@@ -23,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Security
public string Protect(AuthenticationTicket data, string purpose)
{
//create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins
// create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins
var ticket = new AuthenticationTicket(data.Principal,
new AuthenticationProperties(data.Properties.Items)
{

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
{
@@ -35,7 +36,9 @@ namespace Umbraco.Web.BackOffice.Security
private readonly IRequestCache _requestCache;
private readonly IUserService _userService;
private readonly IIpResolver _ipResolver;
private readonly ISystemClock _systemClock;
private readonly BackOfficeSessionIdValidator _sessionIdValidator;
private readonly LinkGenerator _linkGenerator;
public ConfigureBackOfficeCookieOptions(
IUmbracoContextAccessor umbracoContextAccessor,
@@ -47,7 +50,9 @@ namespace Umbraco.Web.BackOffice.Security
IRequestCache requestCache,
IUserService userService,
IIpResolver ipResolver,
BackOfficeSessionIdValidator sessionIdValidator)
ISystemClock systemClock,
BackOfficeSessionIdValidator sessionIdValidator,
LinkGenerator linkGenerator)
{
_umbracoContextAccessor = umbracoContextAccessor;
_securitySettings = securitySettings;
@@ -58,7 +63,9 @@ namespace Umbraco.Web.BackOffice.Security
_requestCache = requestCache;
_userService = userService;
_ipResolver = ipResolver;
_systemClock = systemClock;
_sessionIdValidator = sessionIdValidator;
_linkGenerator = linkGenerator;
}
public void Configure(string name, CookieAuthenticationOptions options)
@@ -98,7 +105,8 @@ namespace Umbraco.Web.BackOffice.Security
_runtimeState,
_hostingEnvironment,
_globalSettings,
_requestCache);
_requestCache,
_linkGenerator);
// _explicitPaths); TODO: Implement this once we do OAuth somehow
@@ -111,7 +119,7 @@ namespace Umbraco.Web.BackOffice.Security
// It would be possible to re-use the default behavior if any of these need to be set but that must be taken into account else
// our back office requests will not function correctly. For now we don't need to set/configure any of these callbacks because
// the defaults work fine with our setup.
OnValidatePrincipal = async ctx =>
{
// We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this)
@@ -131,6 +139,7 @@ namespace Umbraco.Web.BackOffice.Security
await EnsureValidSessionId(ctx);
await securityStampValidator.ValidateAsync(ctx);
EnsureTicketRenewalIfKeepUserLoggedIn(ctx);
// add a claim to track when the cookie expires, we use this to track time remaining
backOfficeIdentity.AddClaim(new Claim(
@@ -140,7 +149,7 @@ namespace Umbraco.Web.BackOffice.Security
UmbracoBackOfficeIdentity.Issuer,
UmbracoBackOfficeIdentity.Issuer,
backOfficeIdentity));
},
OnSigningIn = ctx =>
{
@@ -175,7 +184,6 @@ namespace Umbraco.Web.BackOffice.Security
OnSigningOut = ctx =>
{
//Clear the user's session on sign out
// TODO: We need to test this once we have signout functionality, not sure if the httpcontext.user.identity will still be set here
if (ctx.HttpContext?.User?.Identity != null)
{
var claimsIdentity = ctx.HttpContext.User.Identity as ClaimsIdentity;
@@ -192,7 +200,9 @@ namespace Umbraco.Web.BackOffice.Security
BackOfficeSessionIdValidator.CookieName,
_securitySettings.AuthCookieName,
Constants.Web.PreviewCookieName,
Constants.Security.BackOfficeExternalCookieName
Constants.Security.BackOfficeExternalCookieName,
Constants.Web.AngularCookieName,
Constants.Web.CsrfValidationCookieName,
};
foreach (var cookie in cookies)
{
@@ -218,5 +228,31 @@ namespace Umbraco.Web.BackOffice.Security
if (_runtimeState.Level == RuntimeLevel.Run)
await _sessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
}
/// <summary>
/// Ensures the ticket is renewed if the <see cref="ISecuritySettings.KeepUserLoggedIn"/> is set to true
/// and the current request is for the get user seconds endpoint
/// </summary>
/// <param name="context"></param>
private void EnsureTicketRenewalIfKeepUserLoggedIn(CookieValidatePrincipalContext context)
{
if (!_securitySettings.KeepUserLoggedIn) return;
var currentUtc = _systemClock.UtcNow;
var issuedUtc = context.Properties.IssuedUtc;
var expiresUtc = context.Properties.ExpiresUtc;
if (expiresUtc.HasValue && issuedUtc.HasValue)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
//if it's time to renew, then do it
if (timeRemaining < timeElapsed)
{
context.ShouldRenew = true;
}
}
}
}
}