using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { /// /// A (sub) repository that exposes functionality to modify assigned permissions to a node /// /// /// /// 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 : NPocoRepositoryBase where TEntity : class, IEntity { public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) { } /// /// Returns explicitly defined permissions for a user group for any number of nodes /// /// /// The group ids to lookup permissions for /// /// /// /// /// This method will not support passing in more than 2000 group Ids /// public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds) { var result = new EntityPermissionCollection(); foreach (var groupOfGroupIds in groupIds.InGroupsOf(2000)) { //copy local var localIds = groupOfGroupIds.ToArray(); if (entityIds.Length == 0) { var sql = Sql() .SelectAll() .From() .Where(dto => localIds.Contains(dto.UserGroupId)); var permissions = AmbientScope.Database.Fetch(sql); foreach (var permission in ConvertToPermissionList(permissions)) { result.Add(permission); } } else { //iterate in groups of 2000 since we don't want to exceed the max SQL param count foreach (var groupOfEntityIds in entityIds.InGroupsOf(2000)) { var ids = groupOfEntityIds; var sql = Sql() .SelectAll() .From() .Where(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId)); var permissions = AmbientScope.Database.Fetch(sql); foreach (var permission in ConvertToPermissionList(permissions)) { result.Add(permission); } } } } return result; } /// /// Returns permissions directly assigned to the content items for all user groups /// /// /// public IEnumerable GetPermissionsForEntities(int[] entityIds) { var sql = Sql() .SelectAll() .From() .Where(dto => entityIds.Contains(dto.NodeId)) .OrderBy(dto => dto.NodeId); var result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// /// Returns permissions directly assigned to the content item for all user groups /// /// /// public EntityPermissionCollection GetPermissionsForEntity(int entityId) { var sql = Sql() .SelectAll() .From() .Where(dto => dto.NodeId == entityId) .OrderBy(dto => dto.NodeId); var result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// /// Assigns the same permission set for a single group to any number of entities /// /// /// /// /// /// This will first clear the permissions for this user and entities and recreate them /// public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { if (entityIds.Length == 0) return; var db = AmbientScope.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)"; foreach (var idGroup in entityIds.InGroupsOf(2000)) { db.Execute(sql, new { groupId, nodeIds = idGroup }); } var toInsert = new List(); foreach (var p in permissions) { foreach (var e in entityIds) { toInsert.Add(new UserGroup2NodePermissionDto { NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }); } } db.BulkInsertRecords(toInsert); } /// /// Assigns one permission for a user to many entities /// /// /// /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { var db = AmbientScope.Database; var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; db.Execute(sql, new { groupId, permission = permission.ToString(CultureInfo.InvariantCulture), entityIds }); var actions = entityIds.Select(id => new UserGroup2NodePermissionDto { NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }).ToArray(); db.BulkInsertRecords(actions); } /// /// Assigns one permission to an entity for multiple groups /// /// /// /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { var db = AmbientScope.Database; var groupIdsA = groupIds.ToArray(); const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; db.Execute(sql, new { nodeId = entity.Id, permission = permission.ToString(CultureInfo.InvariantCulture), groupIds = groupIdsA }); var actions = groupIdsA.Select(id => new UserGroup2NodePermissionDto { NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = id }).ToArray(); db.BulkInsertRecords(actions); } /// /// Assigns permissions to an entity for multiple group/permission entries /// /// /// /// /// This will first clear the permissions for this entity then re-create them /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { var db = AmbientScope.Database; const string 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 }); } } db.BulkInsertRecords(toInsert); } #region Not implemented (don't need to for the purposes of this repo) protected override ContentPermissionSet PerformGet(int id) { throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetAll(params int[] ids) { throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetByQuery(IQuery query) { throw new InvalidOperationException("This method won't be implemented."); } protected override Sql GetBaseQuery(bool isCount) { throw new InvalidOperationException("This method won't be implemented."); } protected override string GetBaseWhereClause() { throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable GetDeleteClauses() { return new List(); } protected override Guid NodeObjectTypeId => throw new InvalidOperationException("This property won't be implemented."); protected override void PersistDeletedItem(ContentPermissionSet entity) { throw new InvalidOperationException("This method won't be implemented."); } #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 asIEntity = (IEntity) entity; if (asIEntity.HasIdentity == false) { throw new InvalidOperationException("Cannot create permissions for an entity without an Id"); } ReplaceEntityPermissions(entity); } private static EntityPermissionCollection ConvertToPermissionList(IEnumerable result) { var permissions = new EntityPermissionCollection(); 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).Distinct().ToArray(); permissions.Add(new EntityPermission(permission.Key, np.Key, perms)); } } return permissions; } } }