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
}
}