diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 26e4f79f69..1e457eecf6 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -308,7 +308,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public int[] AllStartContentIds { - get { return _allStartContentIds ?? (_allStartContentIds = StartContentIds.Concat(Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value)).Distinct().ToArray()); } + get { return this.GetAllContentStartNodes(ApplicationContext.Current.Services.EntityService); } } /// @@ -317,7 +317,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public int[] AllStartMediaIds { - get { return _allStartMediaIds ?? (_allStartMediaIds = StartMediaIds.Concat(Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value)).Distinct().ToArray()); } + get { return this.GetAllMediaStartNodes(ApplicationContext.Current.Services.EntityService); } } /// diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 58a844ddf1..4f043c0ec7 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -107,19 +107,36 @@ namespace Umbraco.Core.Models } } - /// - /// Checks if the user has access to the content item based on their start noe - /// - /// - /// - /// - internal static bool HasPathAccess(this IUser user, IContent content) + internal static bool HasContentRootAccess(this IUser user, IEntityService entityService) { - if (user == null) throw new ArgumentNullException("user"); - if (content == null) throw new ArgumentNullException("content"); - return HasPathAccess(content.Path, user.AllStartContentIds, Constants.System.RecycleBinContent); + return HasPathAccess(Constants.System.Root.ToInvariantString(), user.GetAllContentStartNodes(entityService), Constants.System.RecycleBinContent); } - + + internal static bool HasContentBinAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.RecycleBinContent.ToInvariantString(), user.GetAllContentStartNodes(entityService), Constants.System.RecycleBinContent); + } + + internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.Root.ToInvariantString(), user.GetAllMediaStartNodes(entityService), Constants.System.RecycleBinMedia); + } + + internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService) + { + return HasPathAccess(Constants.System.RecycleBinMedia.ToInvariantString(), user.GetAllMediaStartNodes(entityService), Constants.System.RecycleBinMedia); + } + + internal static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService) + { + return HasPathAccess(content.Path, user.GetAllContentStartNodes(entityService), Constants.System.RecycleBinContent); + } + + internal static bool HasPathAccess(this IUser user, IMedia media, IEntityService entityService) + { + return HasPathAccess(media.Path, user.GetAllMediaStartNodes(entityService), Constants.System.RecycleBinMedia); + } + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); @@ -151,19 +168,6 @@ namespace Umbraco.Core.Models return false; } - /// - /// Checks if the user has access to the media item based on their start noe - /// - /// - /// - /// - internal static bool HasPathAccess(this IUser user, IMedia media) - { - if (user == null) throw new ArgumentNullException("user"); - if (media == null) throw new ArgumentNullException("media"); - return HasPathAccess(media.Path, user.AllStartMediaIds, Constants.System.RecycleBinMedia); - } - /// /// Determines whether this user is an admin. /// @@ -176,5 +180,63 @@ namespace Umbraco.Core.Models if (user == null) throw new ArgumentNullException("user"); return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } + + public static int[] GetAllContentStartNodes(this IUser user, IEntityService entityService) + { + var gsn = user.Groups.Where(x => x.StartContentId.HasValue).Select(x => x.StartContentId.Value).Distinct().ToArray(); + var usn = user.StartContentIds; + return CombineStartNodes(gsn, usn, entityService); + } + + public static int[] GetAllMediaStartNodes(this IUser user, IEntityService entityService) + { + var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId.Value).Distinct().ToArray(); + var usn = user.StartMediaIds; + return CombineStartNodes(gsn, usn, entityService); + } + + private static bool StartsWithPath(string test, string path) + { + return test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; + } + + private static int[] CombineStartNodes(int[] groupSn, int[] userSn, IEntityService entityService) + { + // assume groupSn and userSn each don't contain duplicates + + var asn = groupSn.Concat(userSn).Distinct().ToArray(); + var paths = entityService.GetAll(UmbracoObjectTypes.Document, asn).ToDictionary(x => x.Id, x => x.Path); + + paths[-1] = "-1"; // entityService does not get that one + + var lsn = new List(); + foreach (var sn in groupSn) + { + var snp = paths[sn]; + if (lsn.Any(x => StartsWithPath(snp, paths[x]))) continue; // skip if something above this sn + lsn.RemoveAll(x => StartsWithPath(paths[x], snp)); // remove anything below this sn + lsn.Add(sn); + } + + var usn = new List(); + foreach (var sn in userSn) + { + if (groupSn.Contains(sn)) continue; + + var snp = paths[sn]; + if (usn.Any(x => StartsWithPath(paths[x], snp))) continue; // skip if something below this sn + usn.RemoveAll(x => StartsWithPath(snp, paths[x])); // remove anything above this sn + usn.Add(sn); + } + + foreach (var sn in usn) + { + var snp = paths[sn]; + lsn.RemoveAll(x => StartsWithPath(snp, paths[x]) || StartsWithPath(paths[x], snp)); // remove anything above or below this sn + lsn.Add(sn); + } + + return lsn.ToArray(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserExtensionsTests.cs b/src/Umbraco.Tests/Models/UserExtensionsTests.cs index 653b8d5a27..4d3514bf00 100644 --- a/src/Umbraco.Tests/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests/Models/UserExtensionsTests.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -23,7 +24,7 @@ namespace Umbraco.Tests.Models contentMock.Setup(c => c.Path).Returns(contentPath); var content = contentMock.Object; - Assert.AreEqual(outcome, user.HasPathAccess(content)); + Assert.AreEqual(outcome, user.HasPathAccess(content, ApplicationContext.Current.Services.EntityService)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b624041992..8034485785 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1059,17 +1059,12 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + var entityService = ApplicationContext.Current.Services.EntityService; var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.AllStartContentIds, - Constants.System.RecycleBinContent) + ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.AllStartContentIds, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? user.HasContentBinAccess(entityService) + : user.HasPathAccess(contentItem, entityService); if (hasPathAccess == false) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index a2aa8e6b4d..29e8bc9999 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -551,10 +551,10 @@ namespace Umbraco.Web.Editors switch (type) { case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.AllStartContentIds; + aids = Security.CurrentUser.GetAllContentStartNodes(Services.EntityService); break; case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.AllStartMediaIds; + aids = Security.CurrentUser.GetAllMediaStartNodes(Services.EntityService); break; } @@ -695,14 +695,14 @@ namespace Umbraco.Web.Editors type = "media"; AddExamineSearchFrom(searchFrom, sb); - AddExamineUserStartNode(Security.CurrentUser.AllStartMediaIds, sb); + AddExamineUserStartNode(Security.CurrentUser.GetAllMediaStartNodes(Services.EntityService), sb); break; case UmbracoEntityTypes.Document: type = "content"; AddExamineSearchFrom(searchFrom, sb); - AddExamineUserStartNode(Security.CurrentUser.AllStartContentIds, sb); + AddExamineUserStartNode(Security.CurrentUser.GetAllContentStartNodes(Services.EntityService), sb); break; default: @@ -950,10 +950,10 @@ namespace Umbraco.Web.Editors switch (entityType) { case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.AllStartContentIds; + aids = Security.CurrentUser.GetAllContentStartNodes(Services.EntityService); break; case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.AllStartMediaIds; + aids = Security.CurrentUser.GetAllMediaStartNodes(Services.EntityService); break; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 1603615a58..5905af9532 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -254,7 +254,7 @@ namespace Umbraco.Web.Editors private int[] _userStartNodes; protected int[] UserStartNodes { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.AllStartMediaIds); } + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.GetAllMediaStartNodes(Services.EntityService)); } } /// @@ -910,17 +910,12 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + var entityService = Core.ApplicationContext.Current.Services.EntityService; var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.AllStartMediaIds, - Constants.System.RecycleBinMedia) + ? user.HasMediaRootAccess(entityService) : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.AllStartMediaIds, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); + ? user.HasMediaBinAccess(entityService) + : user.HasPathAccess(media, entityService); return hasPathAccess; } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9c15a8b462..5e982dfd9d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -215,7 +215,7 @@ namespace Umbraco.Web.Trees { return false; } - return Security.CurrentUser.HasPathAccess(content); + return Security.CurrentUser.HasPathAccess(content, Services.EntityService); } /// diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 61264e8af8..2a25648f77 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -163,7 +163,7 @@ namespace Umbraco.Web.Trees { return false; } - return Security.CurrentUser.HasPathAccess(media); + return Security.CurrentUser.HasPathAccess(media, Services.EntityService); } } } \ No newline at end of file diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 2fb23571c6..fcfd9ecf44 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -23,7 +23,7 @@ namespace umbraco.BasePages { /// /// umbraco.BasePages.BasePage is the default page type for the umbraco backend. - /// The basepage keeps track of the current user and the page context. But does not + /// The basepage keeps track of the current user and the page context. But does not /// Restrict access to the page itself. /// The keep the page secure, the umbracoEnsuredPage class should be used instead /// @@ -33,7 +33,7 @@ namespace umbraco.BasePages private User _user; private bool _userisValidated = false; private ClientTools _clientTools; - + /// /// The path to the umbraco root folder /// @@ -56,7 +56,7 @@ namespace umbraco.BasePages protected static ISqlHelper SqlHelper { get { return BusinessLogic.Application.SqlHelper; } - } + } /// /// Returns the current ApplicationContext @@ -83,7 +83,7 @@ namespace umbraco.BasePages } /// - /// Returns the current BasePage for the current request. + /// Returns the current BasePage for the current request. /// This assumes that the current page is a BasePage, otherwise, returns null; /// [Obsolete("Should use the Umbraco.Web.UmbracoContext.Current singleton instead to access common methods and properties")] @@ -93,9 +93,9 @@ namespace umbraco.BasePages { var page = HttpContext.Current.CurrentHandler as BasePage; if (page != null) return page; - //the current handler is not BasePage but people might be expecting this to be the case if they + //the current handler is not BasePage but people might be expecting this to be the case if they // are using this singleton accesor... which is legacy code now and shouldn't be used. When people - // start using Umbraco.Web.UI.Pages.BasePage then this will not be the case! So, we'll just return a + // start using Umbraco.Web.UI.Pages.BasePage then this will not be the case! So, we'll just return a // new instance of BasePage as a hack to make it work. if (HttpContext.Current.Items["umbraco.BasePages.BasePage"] == null) { @@ -186,7 +186,7 @@ namespace umbraco.BasePages var identity = HttpContext.Current.GetCurrentIdentity( //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is + // automatically log the back office user in even if it is a front-end request (if there is // a back office user logged in. This can cause problems becaues the identity is changing mid // request. For example: http://issues.umbraco.org/issue/U4-4010 HttpContext.Current.CurrentHandler is Page); @@ -217,7 +217,7 @@ namespace umbraco.BasePages var identity = HttpContext.Current.GetCurrentIdentity( //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is + // automatically log the back office user in even if it is a front-end request (if there is // a back office user logged in. This can cause problems becaues the identity is changing mid // request. For example: http://issues.umbraco.org/issue/U4-4010 HttpContext.Current.CurrentHandler is Page); @@ -234,7 +234,7 @@ namespace umbraco.BasePages { var ticket = HttpContext.Current.GetUmbracoAuthTicket(); if (ticket.Expired) return 0; - var ticks = ticket.Expiration.Ticks - DateTime.Now.Ticks; + var ticks = ticket.Expiration.Ticks - DateTime.Now.Ticks; return ticks; } @@ -251,7 +251,7 @@ namespace umbraco.BasePages var identity = HttpContext.Current.GetCurrentIdentity( //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is + // automatically log the back office user in even if it is a front-end request (if there is // a back office user logged in. This can cause problems becaues the identity is changing mid // request. For example: http://issues.umbraco.org/issue/U4-4010 HttpContext.Current.CurrentHandler is Page); @@ -279,7 +279,7 @@ namespace umbraco.BasePages public static void RenewLoginTimeout() { - HttpContext.Current.RenewUmbracoAuthTicket(); + HttpContext.Current.RenewUmbracoAuthTicket(); } /// @@ -294,8 +294,8 @@ namespace umbraco.BasePages AllowedApplications = u.GetApplications().Select(x => x.alias).ToArray(), RealName = u.Name, Roles = u.GetGroups(), - StartContentNodes = u.UserEntity.AllStartContentIds, - StartMediaNodes = u.UserEntity.AllStartMediaIds, + StartContentNodes = u.UserEntity.GetAllContentStartNodes(ApplicationContext.Current.Services.EntityService), + StartMediaNodes = u.UserEntity.GetAllMediaStartNodes(ApplicationContext.Current.Services.EntityService), Username = u.LoginName, Culture = ui.Culture(u) @@ -342,7 +342,7 @@ namespace umbraco.BasePages } //[Obsolete("Use ClientTools instead")] - //public void reloadParentNode() + //public void reloadParentNode() //{ // ClientTools.ReloadParentNode(true); //} @@ -379,7 +379,7 @@ namespace umbraco.BasePages { base.OnInit(e); - //This must be set on each page to mitigate CSRF attacks which ensures that this unique token + //This must be set on each page to mitigate CSRF attacks which ensures that this unique token // is added to the viewstate of each request if (umbracoUserContextID.IsNullOrWhiteSpace() == false) {