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:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user