diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index 15bdb4cbe9..a1fa57d274 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -11,6 +11,14 @@ namespace Umbraco.Core.Models.Membership { EntityId = entityId; AssignedPermissions = assignedPermissions; + IsDefaultPermissions = false; + } + + public EntityPermission(int entityId, string[] assignedPermissions, bool isDefaultPermissions) + { + EntityId = entityId; + AssignedPermissions = assignedPermissions; + IsDefaultPermissions = isDefaultPermissions; } public int EntityId { get; private set; } @@ -19,6 +27,14 @@ namespace Umbraco.Core.Models.Membership /// The assigned permissions for the user/entity combo /// public string[] AssignedPermissions { get; private set; } + + /// + /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions + /// + /// + /// This will be the case when looking up entity permissions and falling back to the default permissions + /// + public bool IsDefaultPermissions { get; private set; } /// /// Adds additional permissions to an existing instance of diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs index beffc5862d..deebc03401 100644 --- a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs @@ -18,6 +18,14 @@ namespace Umbraco.Core.Models.Membership /// string Alias { get; } + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + IEnumerable Permissions { get; set; } + IEnumerable AllowedSections { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs index b2a7349e81..c1b8d3997f 100644 --- a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs @@ -5,13 +5,15 @@ namespace Umbraco.Core.Models.Membership { public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable { - public ReadOnlyUserGroup(int id, string name, string icon, int? startContentId, int? startMediaId, string @alias, IEnumerable allowedSections) + public ReadOnlyUserGroup(int id, string name, string icon, int? startContentId, int? startMediaId, string @alias, + IEnumerable allowedSections, IEnumerable permissions) { Name = name; Icon = icon; Id = id; Alias = alias; AllowedSections = allowedSections; + Permissions = permissions; //Zero is invalid and will be treated as Null StartContentId = startContentId == 0 ? null : startContentId; @@ -24,6 +26,14 @@ namespace Umbraco.Core.Models.Membership public int? StartContentId { get; private set; } public int? StartMediaId { get; private set; } public string Alias { get; private set; } + + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + public IEnumerable Permissions { get; set; } public IEnumerable AllowedSections { get; private set; } public bool Equals(ReadOnlyUserGroup other) diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 29afc86fc8..012e9b6cc2 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.Membership /// [Serializable] [DataContract(IsReference = true)] - internal class UserGroup : Entity, IUserGroup + internal class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup { private int? _startContentId; private int? _startMediaId; diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index d7625f0fd8..34f4b805f9 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -7,12 +7,17 @@ namespace Umbraco.Core.Models.Membership { public static IReadOnlyUserGroup ToReadOnlyGroup(this IUserGroup group) { - return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections); + //this will generally always be the case + var readonlyGroup = group as IReadOnlyUserGroup; + if (readonlyGroup != null) return readonlyGroup; + + //otherwise create one + return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions); } public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group) { - return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray()); + return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray(), group.DefaultPermissions.ToCharArray().Select(x => x.ToString())); } } } \ 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 499f958449..f07e9c8385 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -470,24 +470,24 @@ namespace Umbraco.Core.Persistence.Repositories 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.Any()) - { - var userGroupPermissions = ( - from perm in parentPermissions - from p in perm.AssignedPermissions - select new EntityPermissionSet.UserGroupPermission(perm.UserGroupId, p)).ToList(); + ////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; - } + // 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; + //} //Create the Content specific data - cmsContent var contentDto = dto.ContentVersionDto.ContentDto; diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index ca9045507b..5ae4efad45 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -35,82 +35,68 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Returns permissions for a given group for any number of nodes + /// Returns explicitly defined permissions for a user group for any number of nodes /// /// /// /// public IEnumerable GetPermissionsForEntities(int groupId, params int[] entityIds) { - var entityIdKey = GetEntityIdKey(entityIds); - return _runtimeCache.GetCacheItem>( - string.Format("{0}{1}{2}", - CacheKeys.UserGroupPermissionsCacheKey, - groupId, - entityIdKey), - () => - { - var whereCriteria = GetPermissionsForEntitiesCriteria(groupId, entityIds); - var sql = new Sql(); - sql.Select("*") - .From() - .Where(whereCriteria); - var result = _unitOfWork.Database.Fetch(sql).ToArray(); - // ToArray() to ensure it's all fetched from the db once - return ConvertToPermissionList(result); - }, - GetCacheTimeout(), - priority: GetCachePriority()); - } - - private static string GetEntityIdKey(int[] entityIds) - { - return string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); - } - - private string GetPermissionsForEntitiesCriteria(int groupId, params int[] entityIds) - { - var whereBuilder = new StringBuilder(); - whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("userGroupId")); - whereBuilder.Append("="); - whereBuilder.Append(groupId); - - if (entityIds.Any()) + //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 + foreach (var groupOfIds in entityIds.InGroupsOf(2000)) { - whereBuilder.Append(" AND "); + var ids = groupOfIds; - //where nodeId = @nodeId1 OR nodeId = @nodeId2, etc... - whereBuilder.Append("("); - for (var index = 0; index < entityIds.Length; index++) - { - var entityId = entityIds[index]; - whereBuilder.Append(_sqlSyntax.GetQuotedColumnName("nodeId")); - whereBuilder.Append("="); - whereBuilder.Append(entityId); - if (index < entityIds.Length - 1) - { - whereBuilder.Append(" OR "); - } - } - - whereBuilder.Append(")"); - } - - return whereBuilder.ToString(); + var sql = new Sql(); + sql.Select("*") + .From(_sqlSyntax) + .Where(dto => dto.UserGroupId == groupId && ids.Contains(dto.NodeId), _sqlSyntax); + var permissions = _unitOfWork.Database.Fetch(sql); + result.AddRange(ConvertToPermissionList(permissions)); + } + return result; + + + //var entityIdKey = GetEntityIdKey(entityIds); + + //return _runtimeCache.GetCacheItem>( + // string.Format("{0}{1}{2}", + // CacheKeys.UserGroupPermissionsCacheKey, + // groupId, + // entityIdKey), + // () => + // { + // var whereCriteria = GetPermissionsForEntitiesCriteria(groupId, entityIds); + // var sql = new Sql(); + // sql.Select("*") + // .From() + // .Where(whereCriteria); + // var result = _unitOfWork.Database.Fetch(sql); + // return ConvertToPermissionList(result); + // }, + // GetCacheTimeout(), + // priority: GetCachePriority()); } - 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 string GetEntityIdKey(int[] entityIds) + //{ + // return string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); + //} - 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 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) { @@ -142,8 +128,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where(dto => dto.NodeId == entityId) .OrderBy(dto => dto.NodeId); - var result = _unitOfWork.Database.Fetch(sql).ToArray(); - // ToArray() to ensure it's all fetched from the db once + var result = _unitOfWork.Database.Fetch(sql); return ConvertToPermissionList(result); } diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 3ac485bf2b..b55048ef4d 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -92,63 +92,63 @@ namespace Umbraco.Core.Services /// User to retrieve permissions for /// Specifiying nothing will return all user permissions for all nodes /// An enumerable list of + /// + /// > This is the ONLY one for permissions from 7.6! + /// IEnumerable GetPermissions(IUser user, params int[] nodeIds); /// /// Get permissions set for a group and optional node Ids /// /// 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 /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of - IEnumerable GetPermissions(string groupAlias, bool directlyAssignedOnly, params int[] nodeIds); + IEnumerable GetPermissions(string groupAlias, bool fallbackToDefaultPermissions, params int[] nodeIds); /// /// Get permissions set for a group and optional node Ids /// /// 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 /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of - IEnumerable GetPermissions(IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds); + IEnumerable GetPermissions(IUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds); /// /// Gets the permissions for the provided user and path /// /// User to check permissions for /// Path to check permissions for - /// String indicating permissions for provided user and path - string GetPermissionsForPath(IUser user, string path); + EntityPermissionSet GetPermissionsForPath(IUser user, string path); /// /// Gets the permissions for the provided group and path /// /// Group alias 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 /// - /// String indicating permissions for provided user and path - string GetPermissionsForPath(string groupAlias, string path, bool directlyAssignedOnly = true); + EntityPermission GetPermissionsForPath(string groupAlias, string path, bool fallbackToDefaultPermissions = false); /// /// 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 /// - /// String indicating permissions for provided user and path - string GetPermissionsForPath(IUserGroup group, string path, bool directlyAssignedOnly = true); + EntityPermission GetPermissionsForPath(IUserGroup group, string path, bool fallbackToDefaultPermissions = false); /// /// Replaces the same permission set for a single group to any number of entities diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index f7cf263e91..b2a1ddec58 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.Services Language = GlobalSettings.DefaultUILanguage, Name = username, RawPasswordValue = passwordValue, - Username = username, + Username = username, IsLockedOut = false, IsApproved = isApproved }; @@ -208,11 +208,11 @@ namespace Umbraco.Core.Services public void Delete(IUser membershipUser) { //disable - membershipUser.IsApproved = false; - + membershipUser.IsApproved = false; + Save(membershipUser); - } - + } + /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// @@ -616,12 +616,12 @@ namespace Umbraco.Core.Services { return repository.GetAllNotInGroup(groupId); } - } - + } + #endregion - + #region Implementation of IUserService - + /// /// Gets an IProfile by User Id. /// @@ -706,8 +706,8 @@ namespace Umbraco.Core.Services repository.AssignGroupPermission(groupId, permission, entityIds); uow.Commit(); } - } - + } + /// /// Gets all UserGroups or those specified as parameters /// @@ -720,8 +720,8 @@ namespace Umbraco.Core.Services var repository = RepositoryFactory.CreateUserGroupRepository(uow); return repository.GetAll(ids).OrderBy(x => x.Name); } - } - + } + public IEnumerable GetUserGroupsByAlias(params string[] aliases) { if (aliases.Length == 0) return Enumerable.Empty(); @@ -788,8 +788,8 @@ namespace Umbraco.Core.Services } var repository = RepositoryFactory.CreateUserGroupRepository(uow); - repository.AddOrUpdateGroupWithUsers(userGroup, userIds); - + repository.AddOrUpdateGroupWithUsers(userGroup, userIds); + uow.Commit(); if (raiseEvents) @@ -838,8 +838,8 @@ namespace Umbraco.Core.Services uow.Commit(); //TODO: Events? } - } - + } + /// /// Get permissions set for a user and node Id /// @@ -848,16 +848,19 @@ namespace Umbraco.Core.Services /// An enumerable list of public IEnumerable GetPermissions(IUser user, params int[] nodeIds) { + if (nodeIds.Length == 0) + return Enumerable.Empty(); + var result = new List(); + foreach (var group in user.Groups) { - //TODO: This may perform horribly :/ - foreach (var permission in GetPermissions(group.Alias, false, nodeIds)) + foreach (var permission in GetPermissions(group, true, nodeIds)) { AddOrAmendPermissionList(result, permission); } - } - + } + return result; } @@ -865,19 +868,57 @@ namespace Umbraco.Core.Services /// Get permissions set for a group and node Id /// /// 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 /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of - public IEnumerable GetPermissions(string groupAlias, bool directlyAssignedOnly, params int[] nodeIds) - { + public IEnumerable GetPermissions(string groupAlias, bool fallbackToDefaultPermissions, params int[] nodeIds) + { + if (nodeIds.Length == 0) + return Enumerable.Empty(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateUserGroupRepository(uow); var group = repository.Get(groupAlias); - return GetPermissionsInternal(repository, group, directlyAssignedOnly, nodeIds); + if (group == null) throw new InvalidOperationException("No group found with alias " + groupAlias); + return GetPermissionsInternal(repository, group.ToReadOnlyGroup(), fallbackToDefaultPermissions, nodeIds); + } + } + + private IEnumerable GetPermissions(IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) + { + if (group == null) throw new ArgumentNullException("group"); + if (nodeIds.Length == 0) + return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserGroupRepository(uow); + return GetPermissionsInternal(repository, group, fallbackToDefaultPermissions, nodeIds); + } + } + + private IEnumerable GetPermissions(int groupId, bool fallbackToDefaultPermissions, params int[] nodeIds) + { + if (nodeIds.Length == 0) + return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserGroupRepository(uow); + + if (fallbackToDefaultPermissions == false) + { + //if fallbackToDefaultPermissions is false, we don't have to lookup the group + return repository.GetPermissionsForEntities(groupId, nodeIds); + } + + var group = repository.Get(groupId); + if (group == null) throw new InvalidOperationException("No group found with id " + groupId); + return GetPermissionsInternal(repository, group.ToReadOnlyGroup(), true, nodeIds); } } @@ -885,35 +926,40 @@ namespace Umbraco.Core.Services /// Get permissions set for a group and optional node Ids /// /// 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 /// /// Specifiying nothing will return all permissions for all nodes /// An enumerable list of - public IEnumerable GetPermissions(IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds) + public IEnumerable GetPermissions(IUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) { if (group == null) throw new ArgumentNullException("group"); using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return GetPermissionsInternal(repository, group, directlyAssignedOnly, nodeIds); + return GetPermissionsInternal(repository, group.ToReadOnlyGroup(), fallbackToDefaultPermissions, nodeIds); } - } - - private IEnumerable GetPermissionsInternal(IUserGroupRepository repository, IUserGroup group, bool directlyAssignedOnly, params int[] nodeIds) + } + + private static IEnumerable GetPermissionsInternal(IUserGroupRepository repository, IReadOnlyUserGroup group, bool fallbackToDefaultPermissions, params int[] nodeIds) { + if (group == null) throw new ArgumentNullException("group"); + + if (nodeIds.Length == 0) + return Enumerable.Empty(); + 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 (directlyAssignedOnly == false) + if (fallbackToDefaultPermissions) { - var missingIds = nodeIds.Except(result.Select(x => x.EntityId)).ToList(); - if (missingIds.Any()) + 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()))); + .Select(i => new EntityPermission(i, group.Permissions.ToArray(), isDefaultPermissions: true))); } } return result; @@ -946,91 +992,163 @@ namespace Umbraco.Core.Services /// /// User to check permissions for /// Path to check permissions for - /// String indicating permissions for provided user and path - public string GetPermissionsForPath(IUser user, string path) - { - var assignedPermissions = GetPermissionsForGroupsAndPath(user.Groups.Select(x => x.Alias), path); - return GetAggregatePermissions(assignedPermissions); + public EntityPermissionSet GetPermissionsForPath(IUser user, string path) + { + var nodeIds = GetIdsFromPath(path); + + if (nodeIds.Length == 0) + return null; + + 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 null; + + 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 - /// List of strings indicating permissions for each groups - private IEnumerable GetPermissionsForGroupsAndPath(IEnumerable groups, string path) + /// 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 => GetPermissionsForPath(g, path, directlyAssignedOnly: false)) - .ToList(); - } - - /// - /// Aggregates a set of permissions strings to return a unique permissions string containing the most permissive set - /// - /// List of permission strings - /// Single permission string - private static string GetAggregatePermissions(IEnumerable assignedPermissions) - { - return string.Join(string.Empty, assignedPermissions - .SelectMany(s => s.ToCharArray()) - .Distinct()); - } - + .Select(g => new + { + group = g.Id, + permissions = GetPermissionsForPath(g, pathIds, fallbackToDefaultPermissions: true) + }) + .ToDictionary(x => x.group, x => x.permissions); + } + /// /// Gets the permissions for the provided group and path /// /// User 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 /// /// String indicating permissions for provided user and path - public string GetPermissionsForPath(string groupAlias, string path, bool directlyAssignedOnly = true) + public EntityPermission GetPermissionsForPath(string groupAlias, string path, bool fallbackToDefaultPermissions = false) { - var nodeId = GetNodeIdFromPath(path); - var permission = GetPermissions(groupAlias, directlyAssignedOnly, nodeId) - .FirstOrDefault(); - return permission != null - ? string.Join(string.Empty, permission.AssignedPermissions) - : string.Empty; - } - + var nodeIds = GetIdsFromPath(path); + //get permissions for all nodes in the path + var permissions = GetPermissions(groupAlias, fallbackToDefaultPermissions, nodeIds); + return GetPermissionsForPath(permissions, nodeIds, fallbackToDefaultPermissions); + } + /// /// 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 /// /// String indicating permissions for provided user and path - public string GetPermissionsForPath(IUserGroup group, string path, bool directlyAssignedOnly = true) + public EntityPermission GetPermissionsForPath(IUserGroup group, string path, bool fallbackToDefaultPermissions = false) { - var nodeId = GetNodeIdFromPath(path); - var permission = GetPermissions(group, directlyAssignedOnly, nodeId) - .FirstOrDefault(); - return permission != null - ? string.Join(string.Empty, permission.AssignedPermissions) - : string.Empty; + var nodeIds = GetIdsFromPath(path); + return GetPermissionsForPath(group.ToReadOnlyGroup(), nodeIds, fallbackToDefaultPermissions); } + private EntityPermission GetPermissionsForPath(IReadOnlyUserGroup group, int[] pathIds, bool fallbackToDefaultPermissions = false) + { + //get permissions for all nodes in the path + var permissions = GetPermissions(group, fallbackToDefaultPermissions, pathIds); + return GetPermissionsForPath(permissions, pathIds, fallbackToDefaultPermissions); + } + + //private EntityPermission GetPermissionsForPath(int groupId, string path, bool fallbackToDefaultPermissions = false) + //{ + // var nodeIds = GetIdsFromPath(path); + // //get permissions for all nodes in the path + // var permissions = GetPermissions(groupId, fallbackToDefaultPermissions, nodeIds); + // return GetPermissionsForPath(permissions, groupId, nodeIds, fallbackToDefaultPermissions); + //} + + /// + /// Returns the permissions for the path ids + /// + /// + /// Must be ordered deepest to shallowest (right to left) + /// + /// + private static EntityPermission GetPermissionsForPath( + IEnumerable pathPermissions, + int[] pathIds, + bool fallbackToDefaultPermissions = false) + { + //get permissions for all nodes in the path + var permissionsByEntityId = pathPermissions.ToDictionary(x => x.EntityId, x => x); + + //then the permissions assigned to the path will be the 'deepest' node found that has permissions + foreach (var id in pathIds) + { + EntityPermission permission; + if (permissionsByEntityId.TryGetValue(id, out permission)) + { + //don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found) + if (permission.IsDefaultPermissions == false) + return permission; + } + } + + //if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified + if (fallbackToDefaultPermissions == false) + return null; + + return permissionsByEntityId[pathIds[0]]; + } /// - /// Parses a path to find the lowermost node id + /// Convert a path to node ids in the order from right to left (deepest to shallowest) /// - /// Path as string - /// Node id - private static int GetNodeIdFromPath(string path) + /// + /// + private int[] GetIdsFromPath(string path) { - return path.Contains(",") - ? int.Parse(path.Substring(path.LastIndexOf(",", StringComparison.Ordinal) + 1)) - : int.Parse(path); - } - + var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.TryConvertTo()) + .Where(x => x.Success) + .Select(x => x.Result) + .Reverse() + .ToArray(); + return nodeIds; + } + + ///// + ///// Parses a path to find the lowermost node id + ///// + ///// Path as string + ///// Node id + //private static int GetNodeIdFromPath(string path) + //{ + // return path.Contains(",") + // ? int.Parse(path.Substring(path.LastIndexOf(",", StringComparison.Ordinal) + 1)) + // : int.Parse(path); + //} + /// /// Checks in a set of permissions associated with a user for those related to a given nodeId /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index a3e5510941..2b9e0b1ed3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -135,11 +135,11 @@ namespace Umbraco.Tests.Persistence.Repositories using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType); - + content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType); + contentTypeRepository.AddOrUpdate(hasPropertiesContentType); - repository.AddOrUpdate(content1); - unitOfWork.Commit(); + repository.AddOrUpdate(content1); + unitOfWork.Commit(); } var versionDtos = new List(); @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Persistence.Repositories VersionId = version, WriterUserId = 0, UpdateDate = versionDate, - TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?) content1.Template.Id + TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?)content1.Template.Id }); } @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(contentItem.Version, versionDtos.Single(x => x.Id == versionDtos.Max(y => y.Id)).VersionId); var allVersions = repository.GetAllVersions(content[0].Id); - var allKnownVersions = versionDtos.Select(x => x.VersionId).Union(new[]{ content1.Version }).ToArray(); + var allKnownVersions = versionDtos.Select(x => x.VersionId).Union(new[] { content1.Version }).ToArray(); Assert.IsTrue(allKnownVersions.ContainsAll(allVersions.Select(x => x.Version))); Assert.IsTrue(allVersions.Select(x => x.Version).ContainsAll(allKnownVersions)); } @@ -547,9 +547,9 @@ namespace Umbraco.Tests.Persistence.Repositories var userGroup = MockedUserGroup.CreateUserGroup("1"); repository.AddOrUpdate(userGroup); unitOfWork.Commit(); - } - - ContentTypeRepository contentTypeRepository; + } + + ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) { var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); @@ -563,7 +563,7 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Commit(); // Act - repository.AssignEntityPermission(parentPage, 'A', new [] { 1 }); + repository.AssignEntityPermission(parentPage, 'A', new[] { 1 }); var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); repository.AddOrUpdate(childPage); unitOfWork.Commit(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index b521172417..93202af102 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1458,6 +1458,178 @@ namespace Umbraco.Tests.Services Assert.That(contents.Any(), Is.False); } + [Test] + public void Ensures_Permissions_Are_Set_On_Copied_Entity_To_Parent_Without_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 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 + 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()); + } + + [Test] + public void Ensures_Permissions_Are_Set_On_Copied_Entity_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 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); + ServiceContext.ContentService.Save(childPage2); + 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()); + + 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()); + } + + //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()); + + descendants = ServiceContext.ContentService.GetDescendants(parentPage).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()); + } + } + [Test] public void Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data() { diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index b0ec03f754..d672229e6b 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -4,14 +4,10 @@ using System.Security.Cryptography; using System.Text; using NUnit.Framework; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using umbraco.BusinessLogic.Actions; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -37,7 +33,7 @@ namespace Umbraco.Tests.Services } [Test] - public void UserService_Get_User_Permissions_For_Unassigned_Permission_Nodes() + public void Get_User_Permissions_For_Unassigned_Permission_Nodes() { // Arrange var userService = ServiceContext.UserService; @@ -55,17 +51,18 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(content); // Act - var permissions = userService.GetPermissions(user, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); + var permissions = userService.GetPermissions(user, content[0].Id, content[1].Id, content[2].Id) + .ToArray(); //assert - Assert.AreEqual(3, permissions.Count()); - Assert.AreEqual(17, permissions.ElementAt(0).AssignedPermissions.Count()); - Assert.AreEqual(17, permissions.ElementAt(1).AssignedPermissions.Count()); - Assert.AreEqual(17, permissions.ElementAt(2).AssignedPermissions.Count()); + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(17, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(17, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(17, permissions[2].AssignedPermissions.Length); } [Test] - public void UserService_Get_User_Permissions_For_Assigned_Permission_Nodes() + public void Get_User_Permissions_For_Assigned_Permission_Nodes() { // Arrange var userService = ServiceContext.UserService; @@ -99,7 +96,7 @@ namespace Umbraco.Tests.Services } [Test] - public void UserService_Get_UserGroup_Assigned_Permissions() + public void Get_UserGroup_Assigned_Permissions() { // Arrange var userService = ServiceContext.UserService; @@ -121,38 +118,6 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); ServiceContext.ContentService.AssignContentPermission(content.ElementAt(2), ActionBrowse.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); - - //assert - Assert.AreEqual(3, permissions.Count()); - Assert.AreEqual(3, permissions.ElementAt(0).AssignedPermissions.Length); - Assert.AreEqual(2, permissions.ElementAt(1).AssignedPermissions.Length); - Assert.AreEqual(1, permissions.ElementAt(2).AssignedPermissions.Length); - } - - [Test] - public void UserService_Get_UserGroup_Assigned_And_Default_Permissions() - { - // Arrange - var userService = ServiceContext.UserService; - var userGroup = CreateTestUserGroup(); - - var contentType = MockedContentTypes.CreateSimpleContentType(); - ServiceContext.ContentTypeService.Save(contentType); - var content = new[] - { - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType), - MockedContent.CreateSimpleContent(contentType) - }; - ServiceContext.ContentService.Save(content); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); - ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); - // Act var permissions = userService.GetPermissions(userGroup, false, content.ElementAt(0).Id, content.ElementAt(1).Id, content.ElementAt(2).Id); @@ -160,7 +125,69 @@ namespace Umbraco.Tests.Services Assert.AreEqual(3, permissions.Count()); Assert.AreEqual(3, permissions.ElementAt(0).AssignedPermissions.Length); Assert.AreEqual(2, permissions.ElementAt(1).AssignedPermissions.Length); - Assert.AreEqual(17, permissions.ElementAt(2).AssignedPermissions.Length); + Assert.AreEqual(1, permissions.ElementAt(2).AssignedPermissions.Length); + } + + [Test] + public void Get_UserGroup_Assigned_And_Default_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var content = new[] + { + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType), + MockedContent.CreateSimpleContent(contentType) + }; + ServiceContext.ContentService.Save(content); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(0), ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(content.ElementAt(1), ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + 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) + .ToArray(); + + //assert + Assert.AreEqual(3, permissions.Length); + Assert.AreEqual(3, permissions[0].AssignedPermissions.Length); + Assert.AreEqual(2, permissions[1].AssignedPermissions.Length); + Assert.AreEqual(17,permissions[2].AssignedPermissions.Length); + } + + [Test] + public void Get_User_Implicit_Permissions() + { + // Arrange + var userService = ServiceContext.UserService; + var userGroup = CreateTestUserGroup(); + + var contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + var parent = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(parent); + var child1 = MockedContent.CreateSimpleContent(contentType, "child1", parent); + ServiceContext.ContentService.Save(child1); + var child2 = MockedContent.CreateSimpleContent(contentType, "child2", child1); + ServiceContext.ContentService.Save(child2); + + ServiceContext.ContentService.AssignContentPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(parent, ActionMove.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id }); + ServiceContext.ContentService.AssignContentPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id }); + + // Act + var permissions = userService.GetPermissionsForPath(userGroup, child2.Path); + + //assert + Assert.AreEqual(3, permissions.AssignedPermissions.Length); } [Test] diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs index aaab898ae2..ee93aec60b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs @@ -89,7 +89,7 @@ namespace umbraco.dialogs if (a.CanBePermissionAssigned == false) continue; var permissions = userService.GetPermissionsForPath(group, _node.Path); - if (permissions.Contains(a.Letter)) + if (permissions.AssignedPermissions.Contains(a.Letter.ToString())) { chk.Checked = true; } diff --git a/src/umbraco.businesslogic/User.cs b/src/umbraco.businesslogic/User.cs index fbf226c39f..0f92a4427f 100644 --- a/src/umbraco.businesslogic/User.cs +++ b/src/umbraco.businesslogic/User.cs @@ -571,7 +571,8 @@ namespace umbraco.BusinessLogic if (_lazyId.HasValue) SetupUser(_lazyId.Value); var userService = ApplicationContext.Current.Services.UserService; - return userService.GetPermissionsForPath(UserEntity, path); + return string.Join("", + userService.GetPermissionsForPath(UserEntity, path).PermissionsSet.SelectMany(x => x.Permission)); } ///