From 044120ffd2e62fbc6188a719dc3f900cba6810bf Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Jul 2017 00:07:51 +1000 Subject: [PATCH] Gets inherited permissions working, adds lots of unit tests, few more tests to write now. --- .../Models/Membership/ContentPermissionSet.cs | 54 ++++ .../Models/Membership/EntityPermission.cs | 11 +- .../Models/Membership/EntityPermissionSet.cs | 76 +++--- .../Membership/UserGroupEntityPermission.cs | 16 -- .../ContentBlueprintRepository.cs | 31 +++ .../Repositories/ContentRepository.cs | 75 ++---- .../Interfaces/IContentRepository.cs | 17 +- .../Interfaces/IUserGroupRepository.cs | 10 +- .../Repositories/PermissionRepository.cs | 254 ++++++++++++------ .../Repositories/SimpleGetRepository.cs | 10 +- .../Repositories/UserGroupRepository.cs | 39 ++- src/Umbraco.Core/Services/ContentService.cs | 18 +- .../Services/ContentServiceExtensions.cs | 2 +- src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IUserService.cs | 10 +- src/Umbraco.Core/Services/UserService.cs | 196 +++++++------- src/Umbraco.Core/StringExtensions.cs | 16 ++ src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../Repositories/ContentRepositoryTest.cs | 43 +-- .../Services/ContentServiceTests.cs | 141 ++-------- .../Services/UserServiceTests.cs | 130 ++++++++- .../Controllers/ContentControllerUnitTests.cs | 41 +-- ...terAllowedOutgoingContentAttributeTests.cs | 8 +- .../Cache/CacheRefresherEventHandler.cs | 7 +- src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/umbraco.businesslogic/User.cs | 2 +- 26 files changed, 725 insertions(+), 501 deletions(-) create mode 100644 src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs delete mode 100644 src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs new file mode 100644 index 0000000000..1345be2c8c --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models.Membership +{ + /// + /// Represents an -> user group & permission key value pair collection + /// + /// + /// This implements purely so it can be used with the repository layer which is why it's explicitly implemented. + /// + public class ContentPermissionSet : EntityPermissionSet, IAggregateRoot + { + private readonly IContent _content; + + public ContentPermissionSet(IContent content, IEnumerable permissionsSet) + : base(content.Id, permissionsSet) + { + _content = content; + } + + public override int EntityId + { + get { return _content.Id; } + } + + #region Explicit implementation of IAggregateRoot + int IEntity.Id + { + get { return EntityId; } + set { throw new NotImplementedException(); } + } + + bool IEntity.HasIdentity + { + get { return EntityId > 0; } + } + + Guid IEntity.Key { get; set; } + + DateTime IEntity.CreateDate { get; set; } + + DateTime IEntity.UpdateDate { get; set; } + + DateTime? IDeletableEntity.DeletedDate { get; set; } + + object IDeepCloneable.DeepClone() + { + throw new NotImplementedException(); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index a1fa57d274..c6d9e8350d 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -7,21 +7,24 @@ namespace Umbraco.Core.Models.Membership /// public class EntityPermission { - public EntityPermission(int entityId, string[] assignedPermissions) + public EntityPermission(int groupId, int entityId, string[] assignedPermissions) { + UserGroupId = groupId; EntityId = entityId; AssignedPermissions = assignedPermissions; IsDefaultPermissions = false; } - public EntityPermission(int entityId, string[] assignedPermissions, bool isDefaultPermissions) + public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions) { + UserGroupId = groupId; EntityId = entityId; AssignedPermissions = assignedPermissions; IsDefaultPermissions = isDefaultPermissions; } public int EntityId { get; private set; } + public int UserGroupId { get; private set; } /// /// The assigned permissions for the user/entity combo @@ -50,7 +53,9 @@ namespace Umbraco.Core.Models.Membership AssignedPermissions = newPermissions .Distinct() .ToArray(); - } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index d91cd3a352..dd9190ae85 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -1,60 +1,56 @@ using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Models.Membership { /// /// Represents an entity -> user group & permission key value pair collection - /// + /// public class EntityPermissionSet { /// - /// The entity id with permissions assigned + /// Returns an empty permission set /// - public int EntityId { get; private set; } + /// + public static EntityPermissionSet Empty() + { + return new EntityPermissionSet(-1, new EntityPermission[0]); + } - /// - /// The key/value pairs of user group id & single permission - /// - public IEnumerable PermissionsSet { get; private set; } - - public EntityPermissionSet(int entityId, IEnumerable permissionsSet) + public EntityPermissionSet(int entityId, IEnumerable permissionsSet) { EntityId = entityId; PermissionsSet = permissionsSet; } - public class UserGroupPermission + /// + /// The entity id with permissions assigned + /// + public virtual int EntityId { get; private set; } + + /// + /// The key/value pairs of user group id & single permission + /// + public IEnumerable PermissionsSet { get; private set; } + + /// + /// Returns the aggregte permissions in the permission set + /// + /// + /// + /// This value is only calculated once + /// + public IEnumerable GetAllPermissions() { - public UserGroupPermission(int groupId, string permission) - { - UserGroupId = groupId; - Permission = permission; - } - - public int UserGroupId { get; private set; } - - public string Permission { get; private set; } - - protected bool Equals(UserGroupPermission other) - { - return UserGroupId == other.UserGroupId && string.Equals(Permission, other.Permission); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((UserGroupPermission) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (UserGroupId * 397) ^ Permission.GetHashCode(); - } - } + return _calculatedPermissions ?? (_calculatedPermissions = + PermissionsSet.SelectMany(x => x.AssignedPermissions).Distinct().ToArray()); } + + private string[] _calculatedPermissions; + + + + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs b/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs deleted file mode 100644 index 619223f01a..0000000000 --- a/src/Umbraco.Core/Models/Membership/UserGroupEntityPermission.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents a user group -> entity permission - /// - public class UserGroupEntityPermission : EntityPermission - { - public UserGroupEntityPermission(int groupId, int entityId, string[] assignedPermissions) - : base(entityId, assignedPermissions) - { - UserGroupId = groupId; - } - - public int UserGroupId { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs new file mode 100644 index 0000000000..4c9021d20a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs @@ -0,0 +1,31 @@ +using System; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Override the base content repository so we can change the node object type + /// + /// + /// It would be nicer if we could separate most of this down into a smaller version of the ContentRepository class, however to do that + /// requires quite a lot of work since we'd need to re-organize the interhitance quite a lot or create a helper class to perform a lot of the underlying logic. + /// + /// TODO: Create a helper method to contain most of the underlying logic for the ContentRepository + /// + internal class ContentBlueprintRepository : ContentRepository + { + public ContentBlueprintRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cacheHelper, logger, syntaxProvider, contentTypeRepository, templateRepository, tagRepository, contentSection) + { + } + + protected override Guid NodeObjectTypeId + { + get { return Constants.ObjectTypes.DocumentBlueprintGuid; } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index f07e9c8385..2c3f126c6e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -20,28 +20,6 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Override the base content repository so we can change the node object type - /// - /// - /// It would be nicer if we could separate most of this down into a smaller version of the ContentRepository class, however to do that - /// requires quite a lot of work since we'd need to re-organize the interhitance quite a lot or create a helper class to perform a lot of the underlying logic. - /// - /// TODO: Create a helper method to conain most of the underlying logic for the ContentRepository - /// - internal class ContentBlueprintRepository : ContentRepository - { - public ContentBlueprintRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cacheHelper, logger, syntaxProvider, contentTypeRepository, templateRepository, tagRepository, contentSection) - { - } - - protected override Guid NodeObjectTypeId - { - get { return Constants.ObjectTypes.DocumentBlueprintGuid; } - } - - } - /// /// Represents a repository for doing CRUD operations for /// @@ -50,9 +28,9 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IContentTypeRepository _contentTypeRepository; private readonly ITemplateRepository _templateRepository; private readonly ITagRepository _tagRepository; - private readonly CacheHelper _cacheHelper; private readonly ContentPreviewRepository _contentPreviewRepository; private readonly ContentXmlRepository _contentXmlRepository; + private readonly PermissionRepository _permissionRepository; public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cacheHelper, logger, syntaxProvider, contentSection) @@ -63,10 +41,10 @@ namespace Umbraco.Core.Persistence.Repositories _contentTypeRepository = contentTypeRepository; _templateRepository = templateRepository; _tagRepository = tagRepository; - _cacheHelper = cacheHelper; _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); - + _permissionRepository = new PermissionRepository(UnitOfWork, cacheHelper, Logger, SqlSyntax); + EnsureUniqueNaming = true; } @@ -468,26 +446,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set entity.Path = nodeDto.Path; entity.SortOrder = sortOrder; - entity.Level = level; - - ////Assign the same permissions to it as the parent node - //// http://issues.umbraco.org/issue/U4-2161 - //var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - //var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); - ////if there are parent permissions then assign them, otherwise leave null and permissions will become the - //// user's default permissions. - //if (parentPermissions.Length > 0) - //{ - // var userGroupPermissions = ( - // from perm in parentPermissions - // from p in perm.AssignedPermissions - // select new EntityPermissionSet.UserGroupPermission(perm.UserGroupId, p)).ToList(); - - // permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userGroupPermissions)); - // //flag the entity's permissions changed flag so we can track those changes. - // //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. - // ((Content)entity).PermissionsChanged = true; - //} + entity.Level = level; //Create the Content specific data - cmsContent var contentDto = dto.ContentVersionDto.ContentDto; @@ -863,8 +822,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", public void ReplaceContentPermissions(EntityPermissionSet permissionSet) { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplaceEntityPermissions(permissionSet); + _permissionRepository.ReplaceEntityPermissions(permissionSet); } public void ClearPublished(IContent content) @@ -880,21 +838,19 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", /// /// public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignEntityPermission(entity, permission, groupIds); + { + _permissionRepository.AssignEntityPermission(entity, permission, groupIds); } /// - /// Returns permissions directly assigned to the content item for all user groups + /// Gets the explicit list of permissions for the content item /// /// /// - public IEnumerable GetPermissionsForEntity(int entityId) + public IEnumerable GetPermissionsForEntity(int entityId) { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - return repo.GetPermissionsForEntity(entityId); - } + return _permissionRepository.GetPermissionsForEntity(entityId); + } /// /// Adds/updates content/published xml @@ -906,6 +862,15 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); } + /// + /// Used to add/update a permission for a content item + /// + /// + public void AddOrUpdatePermissions(ContentPermissionSet permission) + { + _permissionRepository.AddOrUpdate(permission); + } + /// /// Used to remove the content xml for a content item /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 41889de1c4..6f643b3e0b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -57,11 +57,18 @@ namespace Umbraco.Core.Persistence.Repositories void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); /// - /// Gets the list of permissions for the content item + /// Gets the explicit list of permissions for the content item /// /// /// - IEnumerable GetPermissionsForEntity(int entityId); + IEnumerable GetPermissionsForEntity(int entityId); + + ///// + ///// Gets the implicit/inherited list of permissions for the content item + ///// + ///// + ///// + //IEnumerable GetPermissionsForPath(string path); /// /// Used to add/update published xml for the content item @@ -70,6 +77,12 @@ namespace Umbraco.Core.Persistence.Repositories /// void AddOrUpdateContentXml(IContent content, Func xml); + /// + /// Used to add/update a permission for a content item + /// + /// + void AddOrUpdatePermissions(ContentPermissionSet permission); + /// /// Used to remove the content xml for a content item /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs index 3ccfa89885..b018bd3c08 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs @@ -32,6 +32,14 @@ namespace Umbraco.Core.Persistence.Repositories /// Array of entity Ids, if empty will return permissions for the group for all entities IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds); + /// + /// Gets explicilt and default permissions (if requested) permissions for the group for specified entities + /// + /// The group + /// If true will include the group's default permissions if no permissions are explicitly assigned + /// Array of entity Ids, if empty will return permissions for the group for all entities + IEnumerable GetPermissionsForEntities(IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds); + /// /// Replaces the same permission set for a single group to any number of entities /// @@ -47,6 +55,6 @@ namespace Umbraco.Core.Persistence.Repositories /// Permissions as enumerable list of /// Specify the nodes to replace permissions for void AssignGroupPermission(int groupId, char permission, params int[] entityIds); - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index 5ae4efad45..4cad3be7e7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -12,6 +12,9 @@ using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using CacheKeys = Umbraco.Core.Cache.CacheKeys; using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -19,19 +22,18 @@ namespace Umbraco.Core.Persistence.Repositories /// A repository that exposes functionality to modify assigned permissions to a node /// /// - internal class PermissionRepository + /// + /// This repo implements the base class so that permissions can be queued to be persisted + /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// + internal class PermissionRepository : PetaPocoRepositoryBase where TEntity : class, IAggregateRoot { - private readonly IScopeUnitOfWork _unitOfWork; - private readonly IRuntimeCacheProvider _runtimeCache; - private readonly ISqlSyntaxProvider _sqlSyntax; - internal PermissionRepository(IScopeUnitOfWork unitOfWork, CacheHelper cache, ISqlSyntaxProvider sqlSyntax) + public PermissionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) { - _unitOfWork = unitOfWork; - //Make this repository use an isolated cache - _runtimeCache = cache.IsolatedRuntimeCache.GetOrCreateCache(); - _sqlSyntax = sqlSyntax; + } /// @@ -41,7 +43,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// public IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds) - { //var whereCriteria = GetPermissionsForEntitiesCriteria(groupId, entityIds); var result = new List(); //iterate in groups of 2000 since we don't want to exceed the max SQL param count @@ -51,9 +52,9 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); sql.Select("*") - .From(_sqlSyntax) - .Where(dto => dto.UserGroupId == groupId && ids.Contains(dto.NodeId), _sqlSyntax); - var permissions = _unitOfWork.Database.Fetch(sql); + .From(SqlSyntax) + .Where(dto => dto.UserGroupId == groupId && ids.Contains(dto.NodeId), SqlSyntax); + var permissions = UnitOfWork.Database.Fetch(sql); result.AddRange(ConvertToPermissionList(permissions)); } return result; @@ -73,62 +74,62 @@ namespace Umbraco.Core.Persistence.Repositories // sql.Select("*") // .From() // .Where(whereCriteria); - // var result = _unitOfWork.Database.Fetch(sql); + // var result = UnitOfWork.Database.Fetch(sql); // return ConvertToPermissionList(result); // }, // GetCacheTimeout(), // priority: GetCachePriority()); - } - - //private static string GetEntityIdKey(int[] entityIds) - //{ - // return string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); - //} - - //private static TimeSpan GetCacheTimeout() - //{ - // //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will only have this exist in cache for 20 minutes, - // // then it will refresh from the database. - // return new TimeSpan(0, 20, 0); - //} - - //private static CacheItemPriority GetCachePriority() - //{ - // //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will make this priority below average - // return CacheItemPriority.BelowNormal; - //} - - private static IEnumerable ConvertToPermissionList(IEnumerable result) + } + + //private static string GetEntityIdKey(int[] entityIds) + //{ + // return string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); + //} + + //private static TimeSpan GetCacheTimeout() + //{ + // //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will only have this exist in cache for 20 minutes, + // // then it will refresh from the database. + // return new TimeSpan(0, 20, 0); + //} + + //private static CacheItemPriority GetCachePriority() + //{ + // //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will make this priority below average + // return CacheItemPriority.BelowNormal; + //} + + /// + /// Returns permissions directly assigned to the content items for all user groups + /// + /// + /// + public IEnumerable GetPermissionsForEntities(int[] entityIds) { - var permissions = new List(); - var nodePermissions = result.GroupBy(x => x.NodeId); - foreach (var np in nodePermissions) - { - var userGroupPermissions = np.GroupBy(x => x.UserGroupId); - foreach (var permission in userGroupPermissions) - { - var perms = permission.Select(x => x.Permission).ToArray(); - permissions.Add(new UserGroupEntityPermission(permission.Key, permission.First().NodeId, perms)); - } - } + var sql = new Sql(); + sql.Select("*") + .From(SqlSyntax) + .Where(dto => entityIds.Contains(dto.NodeId), SqlSyntax) + .OrderBy(dto => dto.NodeId, SqlSyntax); - return permissions; - } + var result = UnitOfWork.Database.Fetch(sql); + return ConvertToPermissionList(result); + } /// /// Returns permissions directly assigned to the content item for all user groups /// /// /// - public IEnumerable GetPermissionsForEntity(int entityId) + public IEnumerable GetPermissionsForEntity(int entityId) { var sql = new Sql(); sql.Select("*") - .From() - .Where(dto => dto.NodeId == entityId) - .OrderBy(dto => dto.NodeId); + .From(SqlSyntax) + .Where(dto => dto.NodeId == entityId, SqlSyntax) + .OrderBy(dto => dto.NodeId, SqlSyntax); - var result = _unitOfWork.Database.Fetch(sql); + var result = UnitOfWork.Database.Fetch(sql); return ConvertToPermissionList(result); } @@ -143,7 +144,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; @@ -166,10 +167,10 @@ namespace Umbraco.Core.Persistence.Repositories } } - _unitOfWork.Database.BulkInsertRecords(toInsert, _sqlSyntax); + UnitOfWork.Database.BulkInsertRecords(toInsert, SqlSyntax); //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); + UnitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); } @@ -181,7 +182,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; db.Execute(sql, new @@ -198,10 +199,10 @@ namespace Umbraco.Core.Persistence.Repositories UserGroupId = groupId }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + UnitOfWork.Database.BulkInsertRecords(actions, SqlSyntax); //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); + UnitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } @@ -213,7 +214,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; db.Execute(sql, new @@ -230,15 +231,15 @@ namespace Umbraco.Core.Persistence.Repositories UserGroupId = id }).ToArray(); - _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); + UnitOfWork.Database.BulkInsertRecords(actions, SqlSyntax); //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); + UnitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } /// - /// Assigns permissions to an entity for multiple users/permission entries + /// Assigns permissions to an entity for multiple group/permission entries /// /// /// @@ -247,25 +248,118 @@ namespace Umbraco.Core.Persistence.Repositories /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { - var db = _unitOfWork.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; - db.Execute(sql, new { nodeId = permissionSet.EntityId }); - - var actions = permissionSet.PermissionsSet.Select(p => new UserGroup2NodePermissionDto - { - NodeId = permissionSet.EntityId, - Permission = p.Permission, - UserGroupId = p.UserGroupId - }).ToArray(); + var db = UnitOfWork.Database; + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; + db.Execute(sql, new { nodeId = permissionSet.EntityId }); + + var toInsert = new List(); + foreach (var entityPermission in permissionSet.PermissionsSet) + { + foreach (var permission in entityPermission.AssignedPermissions) + { + toInsert.Add(new UserGroup2NodePermissionDto + { + NodeId = permissionSet.EntityId, + Permission = permission, + UserGroupId = entityPermission.UserGroupId + }); + } + } + + UnitOfWork.Database.BulkInsertRecords(toInsert, SqlSyntax); + + //Raise the event + UnitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(permissionSet.PermissionsSet, false)); + - _unitOfWork.Database.BulkInsertRecords(actions, _sqlSyntax); - - //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); - - } - public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; + + #region Not implemented (don't need to for the purposes of this repo) + protected override ContentPermissionSet PerformGet(int id) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new NotImplementedException(); + } + + protected override string GetBaseWhereClause() + { + throw new NotImplementedException(); + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + + protected override void PersistDeletedItem(ContentPermissionSet entity) + { + throw new NotImplementedException(); + } + + #endregion + + /// + /// Used to add or update entity permissions during a content item being updated + /// + /// + protected override void PersistNewItem(ContentPermissionSet entity) + { + //does the same thing as update + PersistUpdatedItem(entity); + } + + /// + /// Used to add or update entity permissions during a content item being updated + /// + /// + protected override void PersistUpdatedItem(ContentPermissionSet entity) + { + var asAggregateRoot = (IAggregateRoot)entity; + if (asAggregateRoot.HasIdentity == false) + { + throw new InvalidOperationException("Cannot create permissions for an entity without an Id"); + } + + ReplaceEntityPermissions(entity); + } + + private static IEnumerable ConvertToPermissionList(IEnumerable result) + { + var permissions = new List(); + var nodePermissions = result.GroupBy(x => x.NodeId); + foreach (var np in nodePermissions) + { + var userGroupPermissions = np.GroupBy(x => x.UserGroupId); + foreach (var permission in userGroupPermissions) + { + var perms = permission.Select(x => x.Permission).ToArray(); + permissions.Add(new EntityPermission(permission.Key, np.Key, perms)); + } + } + + return permissions; + } + + public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index 67c9149422..28e676743f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core.Persistence.Repositories return Database.Fetch(sql).Select(ConvertToEntity); } - protected override sealed IEnumerable PerformGetByQuery(IQuery query) + protected sealed override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); @@ -76,22 +76,22 @@ namespace Umbraco.Core.Persistence.Repositories #region Not implemented and not required - protected override sealed IEnumerable GetDeleteClauses() + protected sealed override IEnumerable GetDeleteClauses() { throw new NotImplementedException(); } - protected override sealed Guid NodeObjectTypeId + protected sealed override Guid NodeObjectTypeId { get { throw new NotImplementedException(); } } - protected override sealed void PersistNewItem(TEntity entity) + protected sealed override void PersistNewItem(TEntity entity) { throw new NotImplementedException(); } - protected override sealed void PersistUpdatedItem(TEntity entity) + protected sealed override void PersistUpdatedItem(TEntity entity) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs index e0717deda4..bd2b8390cd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -22,12 +22,14 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly CacheHelper _cacheHelper; private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository; + private readonly PermissionRepository _permissionRepository; public UserGroupRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cacheHelper, logger, sqlSyntax) { _cacheHelper = cacheHelper; _userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, work, cacheHelper, logger, sqlSyntax); + _permissionRepository = new PermissionRepository(work, _cacheHelper, logger, sqlSyntax); } public const string GetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_"; @@ -97,8 +99,33 @@ namespace Umbraco.Core.Persistence.Repositories /// Array of entity Ids, if empty will return permissions for the group for all entities public IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds) { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - return repo.GetPermissionsForEntities(groupId, entityIds); + return _permissionRepository.GetPermissionsForEntities(groupId, entityIds); + } + + /// + /// Gets explicilt and default permissions (if requested) permissions for the group for specified entities + /// + /// The group + /// If true will include the group's default permissions if no permissions are explicitly assigned + /// Array of entity Ids, if empty will return permissions for the group for all entities + public IEnumerable GetPermissionsForEntities(IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) + { + if (group == null) throw new ArgumentNullException("group"); + + var explicitPermissions = GetPermissionsForEntities(group.Id, nodeIds); + var result = new List(explicitPermissions); + + // If requested, and no permissions are assigned to a particular node, then we will fill in those permissions with the group's defaults + if (fallbackToDefaultPermissions) + { + var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToArray(); + if (missingIds.Length > 0) + { + result.AddRange(missingIds + .Select(i => new EntityPermission(group.Id, i, group.Permissions.ToArray(), isDefaultPermissions: true))); + } + } + return result; } /// @@ -109,8 +136,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. public void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplacePermissions(groupId, permissions, entityIds); + _permissionRepository.ReplacePermissions(groupId, permissions, entityIds); } /// @@ -121,9 +147,8 @@ namespace Umbraco.Core.Persistence.Repositories /// Specify the nodes to replace permissions for public void AssignGroupPermission(int groupId, char permission, params int[] entityIds) { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignPermission(groupId, permission, entityIds); - } + _permissionRepository.AssignPermission(groupId, permission, entityIds); + } #region Overrides of RepositoryBase diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 6e09d5f845..b7649ea382 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -130,11 +130,11 @@ namespace Umbraco.Core.Services } /// - /// Returns permissions directly assigned to the content item for all user groups + /// Returns implicit/inherited permissions assigned to the content item for all user groups /// /// /// - public IEnumerable GetPermissionsForEntity(IContent content) + public IEnumerable GetPermissionsForEntity(IContent content) { using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { @@ -1671,9 +1671,21 @@ namespace Umbraco.Core.Services // Update the create author and last edit author copy.CreatorId = userId; copy.WriterId = userId; + + //get the current permissions, if there are any explicit ones they need to be copied + var currentPermissions = GetPermissionsForEntity(content) + .Where(x => x.IsDefaultPermissions == false).ToArray(); repository.AddOrUpdate(copy); - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + //add permissions + if (currentPermissions.Length > 0) + { + var permissionSet = new ContentPermissionSet(copy, currentPermissions); + repository.AddOrUpdatePermissions(permissionSet); + } + uow.Commit(); // todo - this should flush, not commit //Special case for the associated tags diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 95fe79f9ac..c6dc0274b6 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Services /// public static void RemoveContentPermissions(this IContentService contentService, int contentId) { - contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty())); + contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty())); } /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 289af7324b..251bf169ad 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -151,11 +151,11 @@ namespace Umbraco.Core.Services void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); /// - /// Returns permissions directly assigned to the content item for all user groups + /// Returns implicit/inherited permissions assigned to the content item for all user groups /// /// /// - IEnumerable GetPermissionsForEntity(IContent content); + IEnumerable GetPermissionsForEntity(IContent content); bool SendToPublication(IContent content, int userId = 0); diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index de228412da..c5b282cd66 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -102,8 +102,7 @@ namespace Umbraco.Core.Services /// /// Group to retrieve permissions for /// - /// Flag indicating if we want to get just the permissions directly assigned for the group and path, - /// or fall back to the group's default permissions when nothing is directly assigned + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of @@ -114,16 +113,15 @@ namespace Umbraco.Core.Services /// /// User to check permissions for /// Path to check permissions for - EntityPermissionSet GetPermissionsForPath(IUser user, string path); - + EntityPermissionSet GetPermissionsForPath(IUser user, string path); + /// /// Gets the permissions for the provided group and path /// /// Group to check permissions for /// Path to check permissions for /// - /// Flag indicating if we want to get just the permissions directly assigned for the group and path, - /// or fall back to the group's default permissions when nothing is directly assigned + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set /// EntityPermission GetPermissionsForPath(IUserGroup group, string path, bool fallbackToDefaultPermissions = false); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 841a51456e..52e3e82106 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -865,21 +865,18 @@ namespace Umbraco.Core.Services /// /// Group to retrieve permissions for /// - /// Flag indicating if we want to get just the permissions directly assigned for the group and path, - /// or fall back to the group's default permissions when nothing is directly assigned + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of private IEnumerable GetPermissions(IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) { - if (group == null) throw new ArgumentNullException("group"); - if (nodeIds.Length == 0) - return Enumerable.Empty(); - + if (group == null) throw new ArgumentNullException("group"); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return GetPermissionsInternal(repository, group, fallbackToDefaultPermissions, nodeIds); + return repository.GetPermissionsForEntities(group, fallbackToDefaultPermissions, nodeIds); } } @@ -888,8 +885,7 @@ namespace Umbraco.Core.Services /// /// Group to retrieve permissions for /// - /// Flag indicating if we want to get just the permissions directly assigned for the group and path, - /// or fall back to the group's default permissions when nothing is directly assigned + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of @@ -899,28 +895,8 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return GetPermissionsInternal(repository, group.ToReadOnlyGroup(), fallbackToDefaultPermissions, nodeIds); + return repository.GetPermissionsForEntities(group.ToReadOnlyGroup(), fallbackToDefaultPermissions, nodeIds); } - } - - private static IEnumerable GetPermissionsInternal(IUserGroupRepository repository, IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) - { - if (group == null) throw new ArgumentNullException("group"); - - var explicitPermissions = repository.GetPermissionsForEntities(group.Id, nodeIds); - var result = new List(explicitPermissions); - - // If requested, and no permissions are assigned to a particular node, then we will fill in those permissions with the group's defaults - if (fallbackToDefaultPermissions) - { - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToArray(); - if (missingIds.Length > 0) - { - result.AddRange(missingIds - .Select(i => new EntityPermission(i, group.Permissions.ToArray(), isDefaultPermissions: true))); - } - } - return result; } /// @@ -952,49 +928,18 @@ namespace Umbraco.Core.Services /// Path to check permissions for public EntityPermissionSet GetPermissionsForPath(IUser user, string path) { - var nodeIds = GetIdsFromPath(path); + var nodeIds = path.GetIdsFromPathReversed(); if (nodeIds.Length == 0) return EntityPermissionSet.Empty(); - var permissionsByGroup = GetPermissionsForGroupsAndPath(user.Groups, nodeIds); - - // not sure this will ever happen, it shouldn't since this should return defaults, but maybe those are empty? - if (permissionsByGroup.Count == 0) - return EntityPermissionSet.Empty(); - - var entityId = nodeIds[0]; - - var groupPermissions = new List(); - foreach (var entityPermission in permissionsByGroup) - { - var groupId = entityPermission.Key; - foreach (var assignedPermission in entityPermission.Value.AssignedPermissions) - { - groupPermissions.Add(new EntityPermissionSet.UserGroupPermission(groupId, assignedPermission)); - } - } - - var permissionSet = new EntityPermissionSet(entityId, groupPermissions); - return permissionSet; - } - - /// - /// Retrieves the permissions assigned to each group for a given path - /// - /// List of groups associated with the user - /// Path to check permissions for - /// A dictionary of group ids and their associated node permissions - private IDictionary GetPermissionsForGroupsAndPath(IEnumerable groups, int[] pathIds) - { - return groups - .Select(g => new - { - group = g.Id, - permissions = GetPermissionsForPath(g, pathIds, fallbackToDefaultPermissions: true) - }) - .ToDictionary(x => x.group, x => x.permissions); - } + //collect all permissions structures for all nodes for all groups belonging to the user + var groupPermissions = user.Groups + .Select(g => GetPermissionsForPath(g, nodeIds, fallbackToDefaultPermissions: true)) + .ToArray(); + + return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + } /// /// Gets the permissions for the provided group and path @@ -1002,13 +947,12 @@ namespace Umbraco.Core.Services /// Group to check permissions for /// Path to check permissions for /// - /// Flag indicating if we want to get just the permissions directly assigned for the group and path, - /// or fall back to the group's default permissions when nothing is directly assigned + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set /// /// String indicating permissions for provided user and path public EntityPermission GetPermissionsForPath(IUserGroup group, string path, bool fallbackToDefaultPermissions = false) { - var nodeIds = GetIdsFromPath(path); + var nodeIds = path.GetIdsFromPathReversed(); return GetPermissionsForPath(group.ToReadOnlyGroup(), nodeIds, fallbackToDefaultPermissions); } @@ -1016,17 +960,91 @@ namespace Umbraco.Core.Services { //get permissions for all nodes in the path var permissions = GetPermissions(group, fallbackToDefaultPermissions, pathIds); - return GetPermissionsForPath(permissions, pathIds, fallbackToDefaultPermissions); - } - + return GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions); + } + /// - /// Returns the permissions for the path ids + /// This performs the calculations for inherited nodes based on this http://issues.umbraco.org/issue/U4-10075#comment=67-40085 /// - /// - /// Must be ordered deepest to shallowest (right to left) - /// + /// + /// /// - private static EntityPermission GetPermissionsForPath( + internal static EntityPermissionSet CalculatePermissionsForPathForUser( + EntityPermission[] groupPermissions, + int[] pathIds) + { + // not sure this will ever happen, it shouldn't since this should return defaults, but maybe those are empty? + if (groupPermissions.Length == 0 || pathIds.Length == 0) + return EntityPermissionSet.Empty(); + + //The actual entity id being looked at (deepest part of the path) + var entityId = pathIds[0]; + + var resultPermissions = new List(); + + //create a grouped by dictionary of another grouped by dictionary + var permissionsByGroup = groupPermissions + .GroupBy(x => x.UserGroupId) + .ToDictionary( + x => x.Key, + x => x.GroupBy(a => a.EntityId).ToDictionary(a => a.Key, a => a.ToArray())); + + //iterate through each group + foreach (var byGroup in permissionsByGroup) + { + var added = false; + + //iterate deepest to shallowest + foreach (var pathId in pathIds) + { + EntityPermission[] permissionsForNodeAndGroup; + if (byGroup.Value.TryGetValue(pathId, out permissionsForNodeAndGroup) == false) + continue; + + //In theory there will only be one EntityPermission in this group + // but there's nothing stopping the logic of this method + // from having more so we deal with it here + foreach (var entityPermission in permissionsForNodeAndGroup) + { + if (entityPermission.IsDefaultPermissions == false) + { + //explicit permision found so we'll append it and move on, of course if there was two explicit permissions + //found for this group the ones after this one wouldn't matter but considering there should only be one per + //group anyways, that is fine. + resultPermissions.Add(entityPermission); + added = true; + break; + } + } + + //if the permission has been added for this group and this branch then we can exit this loop + if (added) + break; + } + + if (added == false && byGroup.Value.Count > 0) + { + //if there was no explicit permissions assigned in this branch for this group, then we will + //add the group's default permissions + resultPermissions.Add(byGroup.Value[entityId][0]); + } + + } + + var permissionSet = new EntityPermissionSet(entityId, resultPermissions); + return permissionSet; + } + + /// + /// Returns the resulting permission set for a group for the path based on all permissions provided for the branch + /// + /// The collective set of permissions provided to calculate the resulting permissions set for the path + /// Must be ordered deepest to shallowest (right to left) + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set + /// + /// + internal static EntityPermission GetPermissionsForPathForGroup( IEnumerable pathPermissions, int[] pathIds, bool fallbackToDefaultPermissions = false) @@ -1051,24 +1069,8 @@ namespace Umbraco.Core.Services return null; return permissionsByEntityId[pathIds[0]]; - } - - /// - /// Convert a path to node ids in the order from right to left (deepest to shallowest) - /// - /// - /// - private int[] GetIdsFromPath(string path) - { - var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.TryConvertTo()) - .Where(x => x.Success) - .Select(x => x.Result) - .Reverse() - .ToArray(); - return nodeIds; } - + /// /// Checks in a set of permissions associated with a user for those related to a given nodeId /// diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index dba7f2cfee..0780fe799c 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -41,6 +41,22 @@ namespace Umbraco.Core ToCSharpEscapeChars[escape[0]] = escape[1]; } + /// + /// Convert a path to node ids in the order from right to left (deepest to shallowest) + /// + /// + /// + internal static int[] GetIdsFromPathReversed(this string path) + { + var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.TryConvertTo()) + .Where(x => x.Success) + .Select(x => x.Result) + .Reverse() + .ToArray(); + return nodeIds; + } + /// /// Removes new lines and tabs /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8a635b966c..7269dcd9f6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -345,6 +345,7 @@ + @@ -456,7 +457,6 @@ - @@ -588,6 +588,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 2b9e0b1ed3..f969488055 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -533,48 +533,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(dateValue, persistedTextpage.GetValue(dateTimePropertyAlias)); Assert.AreEqual(persistedTextpage.GetValue(dateTimePropertyAlias), textpage.GetValue(dateTimePropertyAlias)); } - } - - [Test] - public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - - using (var repository = CreateUserGroupRepository(unitOfWork)) - { - var userGroup = MockedUserGroup.CreateUserGroup("1"); - repository.AddOrUpdate(userGroup); - unitOfWork.Commit(); - } - - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentType.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) - }; - var parentPage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(parentPage); - unitOfWork.Commit(); - - // Act - repository.AssignEntityPermission(parentPage, 'A', new[] { 1 }); - var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); - repository.AddOrUpdate(childPage); - unitOfWork.Commit(); - - // Assert - var permissions = repository.GetPermissionsForEntity(childPage.Id); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); - } - - } + } [Test] public void Can_Perform_Add_On_ContentRepository() diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 93202af102..43f049adf4 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1459,7 +1459,7 @@ namespace Umbraco.Tests.Services } [Test] - public void Ensures_Permissions_Are_Set_On_Copied_Entity_To_Parent_Without_Permissions() + public void Ensures_Permissions_Are_Retained_For_Copied_Descendants_With_Explicit_Permissions() { // Arrange var userGroup = MockedUserGroup.CreateUserGroup("1"); @@ -1472,27 +1472,28 @@ namespace Umbraco.Tests.Services }; ServiceContext.ContentTypeService.Save(contentType); - var parentPage = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parentPage); - ServiceContext.ContentService.AssignContentPermission(parentPage, 'A', new[] { 1 }); + var parentPage = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(parentPage); var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); - ServiceContext.ContentService.Save(childPage); - - //Ok, now copy, what should happen is the childPage will not have any permissions assigned since it's new parent - //doesn't have any assigned + ServiceContext.ContentService.Save(childPage); + //assign explicit permissions to the child + ServiceContext.ContentService.AssignContentPermission(childPage, 'A', new[] { userGroup.Id }); + + //Ok, now copy, what should happen is the childPage will retain it's own permissions var parentPage2 = MockedContent.CreateSimpleContent(contentType); ServiceContext.ContentService.Save(parentPage2); var copy = ServiceContext.ContentService.Copy(childPage, parentPage2.Id, false, true); - //Re-get the permissions, since there was none assigned to the new parent, the child shouldn't have any directly assigned - var copyPermissions = ServiceContext.ContentService.GetPermissionsForEntity(copy); - Assert.AreEqual(0, copyPermissions.Count()); + //get the permissions and verify + var permissions = ServiceContext.UserService.GetPermissionsForPath(userGroup, copy.Path, fallbackToDefaultPermissions: true); + Assert.AreEqual(1, permissions.AssignedPermissions.Length); + Assert.AreEqual("A", permissions.AssignedPermissions[0]); } [Test] - public void Ensures_Permissions_Are_Set_On_Copied_Entity_To_Parent_With_Permissions() + public void Ensures_Permissions_Are_Inherited_For_Copied_Descendants() { // Arrange var userGroup = MockedUserGroup.CreateUserGroup("1"); @@ -1507,88 +1508,8 @@ namespace Umbraco.Tests.Services var parentPage = MockedContent.CreateSimpleContent(contentType); ServiceContext.ContentService.Save(parentPage); - ServiceContext.ContentService.AssignContentPermission(parentPage, 'A', new[] { 1 }); + ServiceContext.ContentService.AssignContentPermission(parentPage, 'A', new[] { userGroup.Id }); - var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); - ServiceContext.ContentService.Save(childPage); - - //Ok, now copy, what should happen is the childPage will have it's new parent permissions copied over - var parentPage2 = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parentPage2); - ServiceContext.ContentService.AssignContentPermission(parentPage2, 'B', new[] { 1 }); - - var copy = ServiceContext.ContentService.Copy(childPage, parentPage2.Id, false, true); - - //Re-get the permissions, since there was none assigned to the new parent, the child shouldn't have any directly assigned - var copyPermissions = ServiceContext.ContentService.GetPermissionsForEntity(copy); - Assert.AreEqual(1, copyPermissions.Count()); - Assert.AreEqual("B", copyPermissions.Single().AssignedPermissions.First()); - } - - [Test] - public void Ensures_Permissions_Are_Set_On_Copied_Descendants_To_Parent_With_Permissions() - { - // Arrange - var userGroup = MockedUserGroup.CreateUserGroup("1"); - ServiceContext.UserService.Save(userGroup); - - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentType.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) - }; - ServiceContext.ContentTypeService.Save(contentType); - - var parentPage = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parentPage); - ServiceContext.ContentService.AssignContentPermission(parentPage, 'A', new[] { 1 }); - - var childPage1 = MockedContent.CreateSimpleContent(contentType, "child1", parentPage); - ServiceContext.ContentService.Save(childPage1); - var childPage2 = MockedContent.CreateSimpleContent(contentType, "child2", childPage1); - ServiceContext.ContentService.Save(childPage2); - var childPage3 = MockedContent.CreateSimpleContent(contentType, "child3", childPage2); - ServiceContext.ContentService.Save(childPage3); - - //Ok, now copy, what should happen is the childPage will have it's new parent permissions copied over - var parentPage2 = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parentPage2); - ServiceContext.ContentService.AssignContentPermission(parentPage2, 'B', new[] { 1 }); - - var copy = ServiceContext.ContentService.Copy(childPage1, parentPage2.Id, false, true); - - //Re-get the permissions, since there was none assigned to the new parent, the child shouldn't have any directly assigned - var copyPermissions = ServiceContext.ContentService.GetPermissionsForEntity(copy); - Assert.AreEqual(1, copyPermissions.Count()); - Assert.AreEqual("B", copyPermissions.Single().AssignedPermissions.First()); - - var descendants = ServiceContext.ContentService.GetDescendants(copy).ToArray(); - Assert.AreEqual(2, descendants.Length); - - foreach (var descendant in descendants) - { - var permissions = ServiceContext.ContentService.GetPermissionsForEntity(descendant); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("B", permissions.Single().AssignedPermissions.First()); - } - } - - [Test] - public void Ensures_Permissions_Are_Set_On_Descendants_When_Permissions_Added() - { - // Arrange - var userGroup = MockedUserGroup.CreateUserGroup("1"); - ServiceContext.UserService.Save(userGroup); - - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentType.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) - }; - ServiceContext.ContentTypeService.Save(contentType); - - var parentPage = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(parentPage); var childPage1 = MockedContent.CreateSimpleContent(contentType, "child1", parentPage); ServiceContext.ContentService.Save(childPage1); var childPage2 = MockedContent.CreateSimpleContent(contentType, "child2", childPage1); @@ -1596,37 +1517,33 @@ namespace Umbraco.Tests.Services var childPage3 = MockedContent.CreateSimpleContent(contentType, "child3", childPage2); ServiceContext.ContentService.Save(childPage3); - //ensure there are no permissions on any node - - var permissions = ServiceContext.ContentService.GetPermissionsForEntity(parentPage); - Assert.AreEqual(0, permissions.Count()); - + //Verify that the children have the inherited permissions var descendants = ServiceContext.ContentService.GetDescendants(parentPage).ToArray(); Assert.AreEqual(3, descendants.Length); foreach (var descendant in descendants) { - permissions = ServiceContext.ContentService.GetPermissionsForEntity(descendant); - Assert.AreEqual(0, permissions.Count()); + var permissions = ServiceContext.UserService.GetPermissionsForPath(userGroup, descendant.Path, fallbackToDefaultPermissions:true); + Assert.AreEqual(1, permissions.AssignedPermissions.Length); + Assert.AreEqual("A", permissions.AssignedPermissions[0]); } - - //Ok, now assign permissions to the parent, all descenents should get these right? - ServiceContext.ContentService.AssignContentPermission(parentPage, 'A', new[] { 1 }); - - //re-test - permissions = ServiceContext.ContentService.GetPermissionsForEntity(parentPage); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); + //create a new parent with a new permission structure + var parentPage2 = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(parentPage2); + ServiceContext.ContentService.AssignContentPermission(parentPage2, 'B', new[] { userGroup.Id }); - descendants = ServiceContext.ContentService.GetDescendants(parentPage).ToArray(); + //Now copy, what should happen is the child pages will now have permissions inherited from the new parent + var copy = ServiceContext.ContentService.Copy(childPage1, parentPage2.Id, false, true); + + descendants = ServiceContext.ContentService.GetDescendants(parentPage2).ToArray(); Assert.AreEqual(3, descendants.Length); foreach (var descendant in descendants) { - permissions = ServiceContext.ContentService.GetPermissionsForEntity(descendant); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); + var permissions = ServiceContext.UserService.GetPermissionsForPath(userGroup, descendant.Path, fallbackToDefaultPermissions: true); + Assert.AreEqual(1, permissions.AssignedPermissions.Length); + Assert.AreEqual("B", permissions.AssignedPermissions[0]); } } diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index d672229e6b..ac58f9f734 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -10,6 +11,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Tests.TestHelpers; using Umbraco.Core; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Services; namespace Umbraco.Tests.Services { @@ -151,7 +153,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); // Act - var permissions = userService.GetPermissions(userGroup, true, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id) + var permissions = userService.GetPermissions(userGroup, true, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id) .ToArray(); //assert @@ -161,6 +163,132 @@ namespace Umbraco.Tests.Services Assert.AreEqual(17,permissions[2].AssignedPermissions.Length); } + [Test] + + [Test] + public void Calculate_Permissions_For_User_For_Path_1() + { + //see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085 + // for an overview of what this is testing + + const string path = "-1,1,2,3,4"; + var pathIds = path.GetIdsFromPathReversed(); + + const int groupA = 7; + const int groupB = 8; + const int groupC = 9; + + var userGroups = new Dictionary + { + {groupA, new[] {"S", "D", "F"}}, + {groupB, new[] {"S", "D", "G", "K"}}, + {groupC, new[] {"F", "G"}} + }; + + var permissions = new [] + { + new EntityPermission(groupA, 1, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 2, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 3, userGroups[groupA], isDefaultPermissions:true), + new EntityPermission(groupA, 4, userGroups[groupA], isDefaultPermissions:true), + + new EntityPermission(groupB, 1, userGroups[groupB], isDefaultPermissions:true), + new EntityPermission(groupB, 2, new []{"F", "R"}, isDefaultPermissions:false), + new EntityPermission(groupB, 3, userGroups[groupB], isDefaultPermissions:true), + new EntityPermission(groupB, 4, userGroups[groupB], isDefaultPermissions:true), + + new EntityPermission(groupC, 1, userGroups[groupC], isDefaultPermissions:true), + new EntityPermission(groupC, 2, userGroups[groupC], isDefaultPermissions:true), + new EntityPermission(groupC, 3, new []{"Q", "Z"}, isDefaultPermissions:false), + new EntityPermission(groupC, 4, userGroups[groupC], isDefaultPermissions:true), + }; + + //Permissions for Id 4 + var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds); + Assert.AreEqual(4, result.EntityId); + var allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); + + //Permissions for Id 3 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray()); + Assert.AreEqual(3, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" })); + + //Permissions for Id 2 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray()); + Assert.AreEqual(2, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" })); + + //Permissions for Id 1 + result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray()); + Assert.AreEqual(1, result.EntityId); + allPermissions = result.GetAllPermissions().ToArray(); + Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions)); + Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" })); + + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_1() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] {"A", "B"}; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, new []{"B","C", "D"}, isDefaultPermissions:false), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); + Assert.AreEqual(3, result.AssignedPermissions.Length); + Assert.IsFalse(result.IsDefaultPermissions); + Assert.IsTrue(result.AssignedPermissions.ContainsAll(new[] { "B", "C", "D" })); + Assert.AreEqual(2, result.EntityId); + Assert.AreEqual(9876, result.UserGroupId); + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_2() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] { "A", "B", "C" }; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions:false); + Assert.IsNull(result); + } + + [Test] + public void Determine_Deepest_Explicit_Permissions_For_Group_For_Path_3() + { + var path = "-1,1,2,3"; + var pathIds = path.GetIdsFromPathReversed(); + var defaults = new[] { "A", "B" }; + var permissions = new List + { + new EntityPermission(9876, 1, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 2, defaults, isDefaultPermissions:true), + new EntityPermission(9876, 3, defaults, isDefaultPermissions:true) + }; + var result = UserService.GetPermissionsForPathForGroup(permissions, pathIds, fallbackToDefaultPermissions: true); + Assert.AreEqual(2, result.AssignedPermissions.Length); + Assert.IsTrue(result.IsDefaultPermissions); + Assert.IsTrue(result.AssignedPermissions.ContainsAll(defaults)); + Assert.AreEqual(3, result.EntityId); + Assert.AreEqual(9876, result.UserGroupId); + } + [Test] public void Get_User_Implicit_Permissions() { diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs index f4480ca636..bc1f5dfe77 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs @@ -50,7 +50,8 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; var userServiceMock = new Mock(); var permissions = new List(); - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); var userService = userServiceMock.Object; //act/assert @@ -73,7 +74,8 @@ namespace Umbraco.Tests.Web.Controllers var contentService = contentServiceMock.Object; var userServiceMock = new Mock(); var permissions = new List(); - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -100,9 +102,10 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(1234, new string[]{ "A", "B", "C" }) + new EntityPermission(9876, 1234, new string[]{ "A", "B", "C" }) }; - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -129,9 +132,10 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(1234, new string[]{ "A", "F", "C" }) + new EntityPermission(9876, 1234, new string[]{ "A", "F", "C" }) }; - userServiceMock.Setup(x => x.GetPermissions(user, 1234)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -217,9 +221,10 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[]{ "A" }) }; - userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -241,9 +246,10 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[]{ "A" }) }; - userServiceMock.Setup(x => x.GetPermissions(user, -1)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -264,10 +270,12 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List - { - new EntityPermission(1234, new string[]{ "A" }) - }; - userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); + { + new EntityPermission(9876, 1234, new string[]{ "A" }) + }; + var permissionSet = new EntityPermissionSet(-20, permissions); + + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-20")).Returns(permissionSet); var userService = userServiceMock.Object; //act @@ -289,9 +297,10 @@ namespace Umbraco.Tests.Web.Controllers var userServiceMock = new Mock(); var permissions = new List { - new EntityPermission(1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[]{ "A" }) }; - userServiceMock.Setup(x => x.GetPermissions(user, -20)).Returns(permissions); + var permissionSet = new EntityPermissionSet(1234, permissions); + userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-20")).Returns(permissionSet); var userService = userServiceMock.Object; //act diff --git a/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs index 8e84dcf030..49ce356324 100644 --- a/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs @@ -112,10 +112,10 @@ namespace Umbraco.Tests.Web.Controllers //we're only assigning 3 nodes browse permissions so that is what we expect as a result var permissions = new List { - new EntityPermission(1, new string[]{ "F" }), - new EntityPermission(2, new string[]{ "F" }), - new EntityPermission(3, new string[]{ "F" }), - new EntityPermission(4, new string[]{ "A" }) + new EntityPermission(9876, 1, new string[]{ "F" }), + new EntityPermission(9876, 2, new string[]{ "F" }), + new EntityPermission(9876, 3, new string[]{ "F" }), + new EntityPermission(9876, 4, new string[]{ "A" }) }; userServiceMock.Setup(x => x.GetPermissions(user, ids)).Returns(permissions); var userService = userServiceMock.Object; diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 4b65eb1b3a..8e628204d8 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -599,10 +599,13 @@ namespace Umbraco.Web.Cache #region User/permissions event handlers - static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) + static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) { var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); - groupIds.ForEach(x => DistributedCache.Instance.RefreshUserGroupPermissionsCache(x)); + foreach (var groupId in groupIds) + { + DistributedCache.Instance.RefreshUserGroupPermissionsCache(groupId); + } } static void PermissionDeleted(UserGroupPermission sender, DeleteEventArgs e) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8fd2d8f221..f935673317 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1076,17 +1076,21 @@ namespace Umbraco.Web.Editors return false; } - if (permissionsToCheck == null || permissionsToCheck.Any() == false) + if (permissionsToCheck == null || permissionsToCheck.Length == 0) { return true; } - - var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); + + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + var path = contentItem != null ? contentItem.Path : nodeId.ToString(); + var permission = userService.GetPermissionsForPath(user, path); var allowed = true; foreach (var p in permissionsToCheck) { - if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) { allowed = false; } diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index 0f92a4427f..dcac5a0caf 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -572,7 +572,7 @@ namespace umbraco.BusinessLogic var userService = ApplicationContext.Current.Services.UserService; return string.Join("", - userService.GetPermissionsForPath(UserEntity, path).PermissionsSet.SelectMany(x => x.Permission)); + userService.GetPermissionsForPath(UserEntity, path).GetAllPermissions()); } ///