diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index d2b0278886..c775131fa0 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -162,8 +162,8 @@ namespace Umbraco.Core.Models default: throw new NotSupportedException("Path access is only determined on content or media"); } - } - + } + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); @@ -176,16 +176,73 @@ namespace Umbraco.Core.Models if (startNodeIds.Contains(Constants.System.Root)) return true; - var formattedPath = "," + path + ","; + var formattedPath = string.Concat(",", path, ","); // only users with root access have access to the recycle bin, // if the above check didn't pass then access is denied - if (formattedPath.Contains("," + recycleBinId + ",")) + if (formattedPath.Contains(string.Concat(",", recycleBinId, ","))) return false; // check for a start node in the path - return startNodeIds.Any(x => formattedPath.Contains("," + x + ",")); + return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ","))); } + + internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) + { + switch (recycleBinId) + { + case Constants.System.RecycleBinMedia: + return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); + case Constants.System.RecycleBinContent: + return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); + default: + throw new NotSupportedException("Path access is only determined on content or media"); + } + } + + internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) + { + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); + + hasPathAccess = false; + + // check for no access + if (startNodeIds.Length == 0) + return false; + + // check for root access + if (startNodeIds.Contains(Constants.System.Root)) + { + hasPathAccess = true; + return true; + } + + //is it self? + var self = startNodePaths.Any(x => x == path); + if (self) + { + hasPathAccess = true; + return true; + } + + //is it ancestor? + var ancestor = startNodePaths.Any(x => x.StartsWith(path)); + if (ancestor) + { + hasPathAccess = false; + return true; + } + + //is it descendant? + var descendant = startNodePaths.Any(x => path.StartsWith(x)); + if (descendant) + { + hasPathAccess = true; + return true; + } + + return false; + } /// /// Determines whether this user is an admin. @@ -205,7 +262,7 @@ namespace Umbraco.Core.Models { const string cacheKey = "AllContentStartNodes"; //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); + var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); @@ -220,7 +277,7 @@ namespace Umbraco.Core.Models { const string cacheKey = "AllMediaStartNodes"; //try to look them up from cache so we don't recalculate - var valuesInUserCache = FromUserCache(user, cacheKey); + var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); @@ -228,9 +285,36 @@ namespace Umbraco.Core.Models var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); ToUserCache(user, cacheKey, vals); return vals; + } + + 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(user, cacheKey); + if (valuesInUserCache != null) return valuesInUserCache; + + 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[] GetContentStartNodePaths(this IUser user, IEntityService entityService) + { + const string cacheKey = "ContentStartNodePaths"; + //try to look them up from cache so we don't recalculate + var valuesInUserCache = FromUserCache(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 int[] FromUserCache(IUser user, string cacheKey) + private static T FromUserCache(IUser user, string cacheKey) + where T: class { var entityUser = user as User; if (entityUser == null) return null; @@ -239,12 +323,13 @@ namespace Umbraco.Core.Models { object allContentStartNodes; return entityUser.AdditionalData.TryGetValue(cacheKey, out allContentStartNodes) - ? allContentStartNodes as int[] + ? allContentStartNodes as T : null; } } - private static void ToUserCache(IUser user, string cacheKey, int[] vals) + private static void ToUserCache(IUser user, string cacheKey, T vals) + where T: class { var entityUser = user as User; if (entityUser == null) return; diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 10f3f19c7e..ee38b182b7 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -66,10 +66,14 @@ namespace Umbraco.Web.Trees /// /// internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) - { - var treeNode = GetSingleTreeNode(e, parentId, queryStrings); - var hasAccess = Security.CurrentUser.HasPathAccess(e, Services.EntityService, RecycleBinId); - if (hasAccess == false) + { + bool hasPathAccess; + var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); + if (entityIsAncestorOfStartNodes == false) + return null; + + var treeNode = GetSingleTreeNode(e, parentId, queryStrings); + if (hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; }