From c860069215c92f04e15024955f0c1a404feda6ce Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Sep 2017 00:38:31 +1000 Subject: [PATCH] Updates logic to ensure that if we are rendering site nodes that we don't render all of their children, we will filter out any nodes that do not belong to the user's start nodes' branches --- src/Umbraco.Core/Models/UserExtensions.cs | 105 ++++++++++++++++-- .../Trees/ContentTreeControllerBase.cs | 12 +- 2 files changed, 103 insertions(+), 14 deletions(-) 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; }