Merge pull request #9778 from umbraco/v8/bugfix/broken-user-permissions-caches

Fixes issue with broken caches used for user permissions
This commit is contained in:
Bjarke Berg
2021-02-16 13:41:29 +01:00
committed by GitHub
35 changed files with 350 additions and 228 deletions

View File

@@ -1,4 +1,6 @@
using System;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Membership;
@@ -11,12 +13,27 @@ namespace Umbraco.Core.Models.Identity
private readonly ILocalizedTextService _textService;
private readonly IEntityService _entityService;
private readonly IGlobalSettings _globalSettings;
private readonly AppCaches _appCaches;
public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
[Obsolete("Use constructor specifying all dependencies")]
public IdentityMapDefinition(
ILocalizedTextService textService,
IEntityService entityService,
IGlobalSettings globalSettings)
: this(textService, entityService, globalSettings, Current.AppCaches)
{
}
public IdentityMapDefinition(
ILocalizedTextService textService,
IEntityService entityService,
IGlobalSettings globalSettings,
AppCaches appCaches)
{
_textService = textService;
_entityService = entityService;
_globalSettings = globalSettings;
_appCaches = appCaches;
}
public void DefineMaps(UmbracoMapper mapper)
@@ -46,8 +63,8 @@ namespace Umbraco.Core.Models.Identity
target.Groups = source.Groups.ToArray();
*/
target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService);
target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService);
target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches);
target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService, _appCaches);
target.Email = source.Email;
target.UserName = source.Username;
target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Composing;
@@ -384,11 +385,10 @@ namespace Umbraco.Core.Models.Membership
#endregion
/// <summary>
/// This is used as an internal cache for this entity - specifically for calculating start nodes so we don't re-calculated all of the time
/// </summary>
[IgnoreDataMember]
[DoNotClone]
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This should not be used, it's currently used for only a single edge case - should probably be removed for netcore")]
internal IDictionary<string, object> AdditionalData
{
get
@@ -402,6 +402,8 @@ namespace Umbraco.Core.Models.Membership
[IgnoreDataMember]
[DoNotClone]
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Not used, will be removed in future versions")]
internal object AdditionalDataLock => _additionalDataLock;
protected override void PerformDeepClone(object clone)

View File

@@ -150,48 +150,40 @@ namespace Umbraco.Core.Models
}
}
internal static bool HasContentRootAccess(this IUser user, IEntityService entityService)
{
return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
}
internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
=> ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
internal static bool HasContentBinAccess(this IUser user, IEntityService entityService)
{
return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
}
internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
=> ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService)
{
return ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
}
internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
=> ContentPermissionsHelper.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService)
{
return ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
}
internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches)
=> ContentPermissionsHelper.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService)
internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches)
{
if (content == null) throw new ArgumentNullException(nameof(content));
return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
return ContentPermissionsHelper.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService)
internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService, AppCaches appCaches)
{
if (media == null) throw new ArgumentNullException(nameof(media));
return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
return ContentPermissionsHelper.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
internal static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), Constants.System.RecycleBinContent);
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent);
}
internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService)
internal static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia);
return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia);
}
/// <summary>
@@ -204,84 +196,92 @@ namespace Umbraco.Core.Models
return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias);
}
// calc. start nodes, combining groups' and user's, and excluding what's in the bin
[Obsolete("Use the overload specifying all parameters instead")]
public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService)
{
const string cacheKey = "AllContentStartNodes";
//try to look them up from cache so we don't recalculate
var valuesInUserCache = FromUserCache<int[]>(user, cacheKey);
if (valuesInUserCache != null) return valuesInUserCache;
=> CalculateContentStartNodeIds(user, entityService, Current.AppCaches);
var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray();
var usn = user.StartContentIds;
var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService);
ToUserCache(user, cacheKey, vals);
return vals;
/// <summary>
/// Calculate start nodes, combining groups' and user's, and excluding what's in the bin
/// </summary>
/// <param name="user"></param>
/// <param name="entityService"></param>
/// <param name="runtimeCache"></param>
/// <returns></returns>
public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches)
{
var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id;
var runtimeCache = appCaches.IsolatedCaches.GetOrCreate<IUser>();
var result = runtimeCache.GetCacheItem(cacheKey, () =>
{
var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray();
var usn = user.StartContentIds;
var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService);
return vals;
}, TimeSpan.FromMinutes(2), true);
return result;
}
// calc. start nodes, combining groups' and user's, and excluding what's in the bin
[Obsolete("Use the overload specifying all parameters instead")]
public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService)
{
const string cacheKey = "AllMediaStartNodes";
//try to look them up from cache so we don't recalculate
var valuesInUserCache = FromUserCache<int[]>(user, cacheKey);
if (valuesInUserCache != null) return valuesInUserCache;
=> CalculateMediaStartNodeIds(user, entityService, Current.AppCaches);
var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray();
var usn = user.StartMediaIds;
var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService);
ToUserCache(user, cacheKey, vals);
return vals;
/// <summary>
/// Calculate start nodes, combining groups' and user's, and excluding what's in the bin
/// </summary>
/// <param name="user"></param>
/// <param name="entityService"></param>
/// <param name="runtimeCache"></param>
/// <returns></returns>
public static int[] CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches)
{
var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id;
var runtimeCache = appCaches.IsolatedCaches.GetOrCreate<IUser>();
var result = runtimeCache.GetCacheItem(cacheKey, () =>
{
var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray();
var usn = user.StartMediaIds;
var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService);
return vals;
}, TimeSpan.FromMinutes(2), true);
return result;
}
[Obsolete("Use the overload specifying all parameters instead")]
public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService)
{
const string cacheKey = "MediaStartNodePaths";
//try to look them up from cache so we don't recalculate
var valuesInUserCache = FromUserCache<string[]>(user, cacheKey);
if (valuesInUserCache != null) return valuesInUserCache;
=> GetMediaStartNodePaths(user, entityService, Current.AppCaches);
var startNodeIds = user.CalculateMediaStartNodeIds(entityService);
var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray();
ToUserCache(user, cacheKey, vals);
return vals;
public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches)
{
var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id;
var runtimeCache = appCaches.IsolatedCaches.GetOrCreate<IUser>();
var result = runtimeCache.GetCacheItem(cacheKey, () =>
{
var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches);
var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray();
return vals;
}, TimeSpan.FromMinutes(2), true);
return result;
}
[Obsolete("Use the overload specifying all parameters instead")]
public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService)
=> GetContentStartNodePaths(user, entityService, Current.AppCaches);
public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches)
{
const string cacheKey = "ContentStartNodePaths";
//try to look them up from cache so we don't recalculate
var valuesInUserCache = FromUserCache<string[]>(user, cacheKey);
if (valuesInUserCache != null) return valuesInUserCache;
var startNodeIds = user.CalculateContentStartNodeIds(entityService);
var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray();
ToUserCache(user, cacheKey, vals);
return vals;
}
private static T FromUserCache<T>(IUser user, string cacheKey)
where T: class
{
if (!(user is User entityUser)) return null;
lock (entityUser.AdditionalDataLock)
var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id;
var runtimeCache = appCaches.IsolatedCaches.GetOrCreate<IUser>();
var result = runtimeCache.GetCacheItem(cacheKey, () =>
{
return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes)
? allContentStartNodes as T
: null;
}
}
var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches);
var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray();
return vals;
}, TimeSpan.FromMinutes(2), true);
private static void ToUserCache<T>(IUser user, string cacheKey, T vals)
where T: class
{
if (!(user is User entityUser)) return;
lock (entityUser.AdditionalDataLock)
{
entityUser.AdditionalData[cacheKey] = vals;
}
return result;
}
private static bool StartsWithPath(string test, string path)