using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.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 : EntityRepositoryBase 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 when also passing in entity IDs. /// public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds) { var result = new EntityPermissionCollection(); if (entityIds.Length == 0) { foreach (IEnumerable group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { Sql sql = Sql() .SelectAll() .From() .LeftJoin().On( (left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) .Where(dto => group.Contains(dto.UserGroupId)); List permissions = AmbientScope.Database.Fetch(sql); foreach (EntityPermission permission in ConvertToPermissionList(permissions)) { result.Add(permission); } } } else { foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - groupIds.Length)) { Sql sql = Sql() .SelectAll() .From() .LeftJoin().On( (left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) .Where(dto => groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId)); List permissions = AmbientScope.Database.Fetch(sql); foreach (EntityPermission 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) { Sql sql = Sql() .SelectAll() .From() .LeftJoin() .On((left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) .Where(dto => entityIds.Contains(dto.NodeId)) .OrderBy(dto => dto.NodeId); List 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) { Sql sql = Sql() .SelectAll() .From() .LeftJoin() .On((left, right) => left.NodeId == right.NodeId && left.UserGroupId == right.UserGroupId) .Where(dto => dto.NodeId == entityId) .OrderBy(dto => dto.NodeId); List result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// /// Assigns the same permission set for a single group to any number of entities /// /// /// The permissions to assign or null to remove the connection between group and entityIds /// /// /// 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; } IUmbracoDatabase db = AmbientScope.Database; foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { db.Execute("DELETE FROM umbracoUserGroup2Node WHERE userGroupId = @groupId AND nodeId in (@nodeIds)", new { groupId, nodeIds = group }); db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)", new { groupId, nodeIds = group }); } if (permissions is not null) { var toInsert = new List(); var toInsertPermissions = new List(); foreach (var e in entityIds) { toInsert.Add(new UserGroup2NodeDto() { NodeId = e, UserGroupId = groupId }); foreach (var p in permissions) { toInsertPermissions.Add(new UserGroup2NodePermissionDto { NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }); } } db.BulkInsertRecords(toInsert); db.BulkInsertRecords(toInsertPermissions); } } /// /// Assigns one permission for a user to many entities /// /// /// /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { IUmbracoDatabase db = AmbientScope.Database; db.Execute("DELETE FROM umbracoUserGroup2Node WHERE userGroupId = @groupId AND nodeId in (@entityIds)", new { groupId, entityIds }); db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)", new { groupId, permission = permission.ToString(CultureInfo.InvariantCulture), entityIds }); UserGroup2NodeDto[] actionsPermissions = entityIds.Select(id => new UserGroup2NodeDto { NodeId = id, UserGroupId = groupId }).ToArray(); UserGroup2NodePermissionDto[] actions = entityIds.Select(id => new UserGroup2NodePermissionDto { NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }).ToArray(); db.BulkInsertRecords(actions); db.BulkInsertRecords(actionsPermissions); } /// /// Assigns one permission to an entity for multiple groups /// /// /// /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { IUmbracoDatabase db = AmbientScope.Database; var groupIdsA = groupIds.ToArray(); db.Execute("DELETE FROM umbracoUserGroup2Node WHERE nodeId = @nodeId AND userGroupId in (@groupIds)", new { nodeId = entity.Id, groupIds = groupIdsA }); db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)", new { nodeId = entity.Id, permission = permission.ToString(CultureInfo.InvariantCulture), groupIds = groupIdsA }); UserGroup2NodePermissionDto[] actionsPermissions = groupIdsA.Select(id => new UserGroup2NodePermissionDto { NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = id }).ToArray(); UserGroup2NodeDto[] actions = groupIdsA.Select(id => new UserGroup2NodeDto { NodeId = entity.Id, UserGroupId = id }).ToArray(); db.BulkInsertRecords(actions); db.BulkInsertRecords(actionsPermissions); } /// /// 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) { IUmbracoDatabase db = AmbientScope.Database; db.Execute("DELETE FROM umbracoUserGroup2Node WHERE nodeId = @nodeId", new { nodeId = permissionSet.EntityId }); db.Execute("DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId", new { nodeId = permissionSet.EntityId }); var toInsert = new List(); var toInsertPermissions = new List(); foreach (EntityPermission entityPermission in permissionSet.PermissionsSet) { toInsert.Add(new UserGroup2NodeDto { NodeId = permissionSet.EntityId, UserGroupId = entityPermission.UserGroupId }); foreach (var permission in entityPermission.AssignedPermissions) { toInsertPermissions.Add(new UserGroup2NodePermissionDto { NodeId = permissionSet.EntityId, Permission = permission, UserGroupId = entityPermission.UserGroupId }); } } db.BulkInsertRecords(toInsert); db.BulkInsertRecords(toInsertPermissions); } /// /// 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(); IEnumerable> nodePermissions = result.GroupBy(x => x.NodeId); foreach (IGrouping np in nodePermissions) { IEnumerable> userGroupPermissions = np.GroupBy(x => x.UserGroupId); foreach (IGrouping permission in userGroupPermissions) { var perms = permission.Select(x => x.Permission).Distinct().ToArray(); // perms can contain null if there are no permissions assigned, but the node is chosen in the UI. permissions.Add(new EntityPermission(permission.Key, np.Key, perms.WhereNotNull().ToArray())); } } return permissions; } #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() => new List(); protected override void PersistDeletedItem(ContentPermissionSet entity) => throw new InvalidOperationException("This method won't be implemented."); #endregion } }