Updates the BulkInsertRecords method to optionally close the trans - shouldn't by default. Updates how permissions are handled in the new services and exposes another method, ensures it's all wrapped in trans and ensures that cache is cleared properly. Fixes: U4-4213 "Replace child node permissions" does not work if all permissions cleared

This commit is contained in:
Shannon
2014-02-17 17:45:59 +11:00
parent 4d2d9156ba
commit feefb052fd
17 changed files with 232 additions and 181 deletions

View File

@@ -57,11 +57,20 @@ namespace Umbraco.Core.Persistence
using (var tr = db.GetTransaction())
{
db.BulkInsertRecords(collection, tr);
db.BulkInsertRecords(collection, tr, true);
}
}
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection, Transaction tr)
/// <summary>
/// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction
/// when finished
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="db"></param>
/// <param name="collection"></param>
/// <param name="tr"></param>
/// <param name="commitTrans"></param>
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection, Transaction tr, bool commitTrans = false)
{
//don't do anything if there are no records.
if (collection.Any() == false)
@@ -95,11 +104,17 @@ namespace Umbraco.Core.Persistence
}
}
tr.Complete();
if (commitTrans)
{
tr.Complete();
}
}
catch
{
tr.Dispose();
if (commitTrans)
{
tr.Dispose();
}
throw;
}
}

View File

@@ -530,6 +530,12 @@ namespace Umbraco.Core.Persistence.Repositories
return GetByVersion(dto.ContentVersionDto.VersionId);
}
/// <summary>
/// Assigns one permission to an entity for multiple users
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
public void AssignEntityPermissions(IContent entity, char permission, IEnumerable<int> userIds)
{
var repo = new PermissionRepository<IContent>(UnitOfWork, _cacheHelper);

View File

@@ -23,6 +23,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns>An enumerable list of <see cref="IContent"/></returns>
IEnumerable<IContent> GetByPublishedVersion(IQuery<IContent> query);
/// <summary>
/// Assigns a single permission to the current content item for the specified user ids
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
void AssignEntityPermissions(IContent entity, char permission, IEnumerable<int> userIds);
/// <summary>

View File

@@ -47,5 +47,13 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="entityIds"></param>
/// <returns></returns>
IEnumerable<EntityPermission> GetUserPermissionsForEntities(int userId, params int[] entityIds);
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
void AssignUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds);
}
}

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Web.Caching;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
@@ -13,6 +14,7 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
using CacheKeys = Umbraco.Core.Cache.CacheKeys;
using Umbraco.Core.Cache;
@@ -21,7 +23,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <summary>
/// A repository that exposes functionality to modify assigned permissions to a node
/// </summary>
/// <typeparam name="TId"></typeparam>
/// <typeparam name="TEntity"></typeparam>
internal class PermissionRepository<TEntity>
where TEntity : class, IAggregateRoot
@@ -41,7 +42,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="userId"></param>
/// <param name="entityIds"></param>
/// <returns></returns>
internal IEnumerable<EntityPermission> GetUserPermissionsForEntities(int userId, params int[] entityIds)
public IEnumerable<EntityPermission> GetUserPermissionsForEntities(int userId, params int[] entityIds)
{
var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture)));
return _cache.RuntimeCache.GetCacheItem<IEnumerable<EntityPermission>>(
@@ -99,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <param name="entityId"></param>
/// <returns></returns>
internal IEnumerable<EntityPermission> GetPermissionsForEntity(int entityId)
public IEnumerable<EntityPermission> GetPermissionsForEntity(int entityId)
{
var sql = new Sql();
sql.Select("*")
@@ -112,7 +113,110 @@ namespace Umbraco.Core.Persistence.Repositories
return ConvertToPermissionList(result);
}
private IEnumerable<EntityPermission> ConvertToPermissionList(IEnumerable<User2NodePermissionDto> result)
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
public void AssignUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds)
{
var db = _unitOfWork.Database;
using (var trans = db.GetTransaction())
{
db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)",
new {userId = userId, nodeIds = entityIds});
var toInsert = new List<User2NodePermissionDto>();
foreach (var p in permissions)
{
foreach (var e in entityIds)
{
toInsert.Add(new User2NodePermissionDto
{
NodeId = e,
Permission = p.ToString(CultureInfo.InvariantCulture),
UserId = userId
});
}
}
_unitOfWork.Database.BulkInsertRecords(toInsert, trans);
trans.Complete();
//Raise the event
AssignedPermissions.RaiseEvent(
new SaveEventArgs<EntityPermission>(ConvertToPermissionList(toInsert), false), this);
}
}
/// <summary>
/// Assigns one permission to an entity for multiple users
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
/// <remarks>
/// This will first clear the permissions for this entity then re-create them
/// </remarks>
public void AssignEntityPermissions(TEntity entity, char permission, IEnumerable<int> userIds)
{
var db = _unitOfWork.Database;
using (var trans = db.GetTransaction())
{
db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new {nodeId = entity.Id});
var actions = userIds.Select(id => new User2NodePermissionDto
{
NodeId = entity.Id,
Permission = permission.ToString(CultureInfo.InvariantCulture),
UserId = id
}).ToArray();
_unitOfWork.Database.BulkInsertRecords(actions, trans);
trans.Complete();
//Raise the event
AssignedPermissions.RaiseEvent(
new SaveEventArgs<EntityPermission>(ConvertToPermissionList(actions), false), this);
}
}
/// <summary>
/// Assigns permissions to an entity for multiple users/permission entries
/// </summary>
/// <param name="entity"></param>
/// <param name="userPermissions">
/// A key/value pair list containing a userId and a permission to assign
/// </param>
/// <remarks>
/// This will first clear the permissions for this entity then re-create them
/// </remarks>
public void AssignEntityPermissions(TEntity entity, IEnumerable<Tuple<int, string>> userPermissions)
{
var db = _unitOfWork.Database;
using (var trans = db.GetTransaction())
{
db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new { nodeId = entity.Id });
var actions = userPermissions.Select(p => new User2NodePermissionDto
{
NodeId = entity.Id,
Permission = p.Item2,
UserId = p.Item1
}).ToArray();
_unitOfWork.Database.BulkInsertRecords(actions);
//Raise the event
AssignedPermissions.RaiseEvent(
new SaveEventArgs<EntityPermission>(ConvertToPermissionList(actions), false), this);
}
}
private static IEnumerable<EntityPermission> ConvertToPermissionList(IEnumerable<User2NodePermissionDto> result)
{
var permissions = new List<EntityPermission>();
var nodePermissions = result.GroupBy(x => x.NodeId);
@@ -128,116 +232,6 @@ namespace Umbraco.Core.Persistence.Repositories
return permissions;
}
/// <summary>
/// Assigns one permission to an entity for multiple users
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
internal void AssignEntityPermissions(TEntity entity, char permission, IEnumerable<int> userIds)
{
var actions = userIds.Select(id => new User2NodePermissionDto
{
NodeId = entity.Id,
Permission = permission.ToString(CultureInfo.InvariantCulture),
UserId = (int)id
});
_unitOfWork.Database.BulkInsertRecords(actions);
}
/// <summary>
/// Assigns permissions to an entity for multiple users/permission entries
/// </summary>
/// <param name="entity"></param>
/// <param name="userPermissions">
/// A key/value pair list containing a userId and a permission to assign
/// </param>
internal void AssignEntityPermissions(TEntity entity, IEnumerable<Tuple<int, string>> userPermissions)
{
var actions = userPermissions.Select(p => new User2NodePermissionDto
{
NodeId = entity.Id,
Permission = p.Item2,
UserId = p.Item1
});
_unitOfWork.Database.BulkInsertRecords(actions);
}
/// <summary>
/// Replace permissions for an entity for multiple users
/// </summary>
/// <param name="entity"></param>
/// <param name="permissions"></param>
/// <param name="userIds"></param>
internal void ReplaceEntityPermissions(TEntity entity, string permissions, IEnumerable<int> userIds)
{
_unitOfWork.Database.Update<User2NodePermissionDto>(
GenerateReplaceEntityPermissionsSql(entity.Id, permissions, userIds.ToArray()));
}
/// <summary>
/// An overload to replace entity permissions and all replace all descendant permissions
/// </summary>
/// <param name="entity"></param>
/// <param name="permissions"></param>
/// <param name="getDescendantIds">
/// A callback to get the descendant Ids of the current entity
/// </param>
/// <param name="userIds"></param>
internal void ReplaceEntityPermissions(TEntity entity, string permissions, Func<IEntity, IEnumerable<int>> getDescendantIds, IEnumerable<int> userIds)
{
_unitOfWork.Database.Update<User2NodePermissionDto>(
GenerateReplaceEntityPermissionsSql(
new[] { entity.Id }.Concat(getDescendantIds(entity)).ToArray(),
permissions,
userIds.ToArray()));
}
internal static string GenerateReplaceEntityPermissionsSql(int entityId, string permissions, int[] userIds)
{
return GenerateReplaceEntityPermissionsSql(new[] { entityId }, permissions, userIds);
}
internal static string GenerateReplaceEntityPermissionsSql(int[] entityIds, string permissions, int[] userIds)
{
//create the "SET" clause of the update statement
var sqlSet = string.Format("SET {0}={1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("permission"),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedValue(permissions));
//build the nodeIds part of the where clause
var sqlNodeWhere = BuildOrClause(entityIds, "nodeId");
//build up the userIds part of the where clause
var userWhereBuilder = BuildOrClause(userIds, "userId");
var sqlWhere = new Sql();
sqlWhere.Where(string.Format("{0} AND {1}", sqlNodeWhere, userWhereBuilder));
return string.Format("{0} {1}", sqlSet, sqlWhere.SQL);
}
private static string BuildOrClause<T>(IEnumerable<T> ids, string colName)
{
var asArray = ids.ToArray();
var userWhereBuilder = new StringBuilder();
userWhereBuilder.Append("(");
for (var index = 0; index < asArray.Length; index++)
{
var userId = asArray[index];
userWhereBuilder.Append(SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(colName));
userWhereBuilder.Append("=");
userWhereBuilder.Append(userId);
if (index < asArray.Length - 1)
{
userWhereBuilder.Append(" OR ");
}
}
userWhereBuilder.Append(")");
return userWhereBuilder.ToString();
}
public static event TypedEventHandler<PermissionRepository<TEntity>, SaveEventArgs<EntityPermission>> AssignedPermissions;
}
}

View File

@@ -324,6 +324,18 @@ namespace Umbraco.Core.Persistence.Repositories
return repo.GetUserPermissionsForEntities(userId, entityIds);
}
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
public void AssignUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds)
{
var repo = new PermissionRepository<IContent>(UnitOfWork, _cacheHelper);
repo.AssignUserPermissions(userId, permissions, entityIds);
}
#endregion
private IEnumerable<IUser> ConvertFromDtos(IEnumerable<UserDto> dtos)

View File

@@ -1511,6 +1511,8 @@ namespace Umbraco.Core.Services
//bulk insert it into the database
uow.Database.BulkInsertRecords(xmlItems, tr);
tr.Complete();
}
Audit.Add(AuditTypes.Publish, "RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1);

View File

@@ -214,17 +214,17 @@ namespace Umbraco.Core.Services
{
using (var uow = _uowProvider.GetUnitOfWork())
{
var sortOrderObj =
uow.Database.ExecuteScalar<object>(
"SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id });
int sortOrder;
if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false)
{
sortOrder = 1;
}
using (var transaction = uow.Database.GetTransaction())
{
var sortOrderObj =
uow.Database.ExecuteScalar<object>(
"SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id });
int sortOrder;
if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false)
{
sortOrder = 1;
}
foreach (var value in values)
{
var dto = new DataTypePreValueDto { DataTypeNodeId = id, Value = value, SortOrder = sortOrder };

View File

@@ -53,6 +53,14 @@ namespace Umbraco.Core.Services
/// <returns></returns>
IEnumerable<EntityPermission> GetPermissions(IUser user, params int[] nodeIds);
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
void AssignUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds);
#region User types
IEnumerable<IUserType> GetAllUserTypes(params int[] ids);

View File

@@ -938,6 +938,8 @@ namespace Umbraco.Core.Services
//bulk insert it into the database
uow.Database.BulkInsertRecords(xmlItems, tr);
tr.Complete();
}
Audit.Add(AuditTypes.Publish, "RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1);

View File

@@ -972,6 +972,8 @@ namespace Umbraco.Core.Services
//bulk insert it into the database
uow.Database.BulkInsertRecords(xmlItems, tr);
tr.Complete();
}
}
}

View File

@@ -398,6 +398,21 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
public void AssignUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateUserRepository(uow))
{
repository.AssignUserPermissions(userId, permissions, entityIds);
}
}
public IEnumerable<IUserType> GetAllUserTypes(params int[] ids)
{
var uow = _uowProvider.GetUnitOfWork();

View File

@@ -12,25 +12,6 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class PetaPocoSqlTests : BaseUsingSqlCeSyntax
{
[Test]
public void Generate_Replace_Entity_Permissions_Test()
{
// Act
var sql = PermissionRepository<IContent>.GenerateReplaceEntityPermissionsSql(123, "A", new int[] {10, 11, 12});
// Assert
Assert.AreEqual(@"SET [permission]='A' WHERE (([nodeId]=123) AND ([userId]=10 OR [userId]=11 OR [userId]=12))", sql);
}
[Test]
public void Generate_Replace_Entity_Permissions_With_Descendants_Test()
{
// Act
var sql = PermissionRepository<IContent>.GenerateReplaceEntityPermissionsSql(new[] { 123, 456 }, "A", new int[] { 10, 11, 12 });
// Assert
Assert.AreEqual(@"SET [permission]='A' WHERE (([nodeId]=123 OR [nodeId]=456) AND ([userId]=10 OR [userId]=11 OR [userId]=12))", sql);
}
[Test]
public void Can_Select_From_With_Type()

View File

@@ -229,6 +229,8 @@ namespace Umbraco.Tests.Services
DatabaseContext.Database.BulkInsertRecords(xmlItems, tr);
tr.Complete();
}
}
}

View File

@@ -1,8 +1,11 @@
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Services;
using umbraco;
using umbraco.BusinessLogic;
@@ -11,6 +14,7 @@ using umbraco.cms.businesslogic.member;
using System.Linq;
using umbraco.cms.businesslogic.web;
using Content = Umbraco.Core.Models.Content;
using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs;
using Macro = umbraco.cms.businesslogic.macro.Macro;
using Member = umbraco.cms.businesslogic.member.Member;
using Template = umbraco.cms.businesslogic.template.Template;
@@ -95,6 +99,7 @@ namespace Umbraco.Web.Cache
Permission.New += PermissionNew;
Permission.Updated += PermissionUpdated;
Permission.Deleted += PermissionDeleted;
PermissionRepository<IContent>.AssignedPermissions += CacheRefresherEventHandler_AssignedPermissions;
//Bind to template events
//NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979
@@ -423,7 +428,13 @@ namespace Umbraco.Web.Cache
#endregion
#region User event handlers
#region User/permissions event handlers
static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository<IContent> sender, SaveEventArgs<EntityPermission> e)
{
var userIds = e.SavedEntities.Select(x => x.UserId).Distinct();
userIds.ForEach(x => DistributedCache.Instance.RefreshUserPermissionsCache(x));
}
static void PermissionDeleted(UserPermission sender, DeleteEventArgs e)
{

View File

@@ -8,6 +8,7 @@ using System.Collections;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Data;
using Umbraco.Core;
using Umbraco.Web;
using Umbraco.Web.Security;
using umbraco;
@@ -27,17 +28,11 @@ namespace umbraco.cms.presentation.user
/// </summary>
public class UserPermissions
{
User m_user;
readonly User _user;
public UserPermissions(User user)
{
m_user = user;
}
private static ISqlHelper SqlHelper
{
get { return Application.SqlHelper; }
_user = user;
}
/// <summary>
@@ -70,31 +65,32 @@ namespace umbraco.cms.presentation.user
//get the complete list of node ids that this change will affect
var allNodes = new List<int>();
if (replaceChildren)
{
foreach (var nodeId in nodeIDs)
{
allNodes.Add(nodeId);
allNodes.AddRange(FindChildNodes(nodeId));
}
}
else
{
allNodes.AddRange(nodeIDs);
//First remove all permissions for all nodes in question
Permission.DeletePermissions(m_user.Id, allNodes.ToArray());
}
//if permissions are to be assigned, then assign them
if (permissions.Count > 0)
{
foreach (var oPer in permissions)
{
InsertPermissions(allNodes.ToArray(), oPer);
}
ApplicationContext.Current.Services.UserService.AssignUserPermissions(
_user.Id, permissions.Select(x => x.Letter), allNodes.ToArray());
}
else
{
//If there are NO permissions for this node, we need to assign the ActionNull permission otherwise
//the node will inherit from it's parent.
InsertPermissions(nodeIDs, ActionNull.Instance);
ApplicationContext.Current.Services.UserService.AssignUserPermissions(
_user.Id, new[] { ActionNull.Instance.Letter }, allNodes.ToArray());
}
}
/// <summary>
@@ -108,7 +104,7 @@ namespace umbraco.cms.presentation.user
if (path != "")
{
//get the user and their permissions
string permissions = m_user.GetPermissions(path);
string permissions = _user.GetPermissions(path);
return umbraco.BusinessLogic.Actions.Action.FromString(permissions);
}
return null;
@@ -149,11 +145,5 @@ namespace umbraco.cms.presentation.user
}
return nodeIds;
}
private void InsertPermissions(IEnumerable<int> nodeIDs, IAction permission)
{
Permission.MakeNew(m_user, nodeIDs.Select(x => new CMSNode(x)), permission.Letter, true);
}
}
}

View File

@@ -2,8 +2,10 @@ using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Umbraco.Core;
using Umbraco.Core.Events;
using umbraco.DataLayer;
using umbraco.cms.businesslogic;
@@ -170,7 +172,7 @@ namespace umbraco.BusinessLogic
}
private static string Converter(int from)
{
return from.ToString();
return from.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
@@ -189,16 +191,11 @@ namespace umbraco.BusinessLogic
[MethodImpl(MethodImplOptions.Synchronized)]
public static void UpdateCruds(User user, CMSNode node, string permissions)
{
// delete all settings on the node for this user
//false = do not raise events
DeletePermissions(user, node, false);
ApplicationContext.Current.Services.UserService.AssignUserPermissions(
user.Id,
permissions.ToCharArray(),
node.Id);
// Loop through the permissions and create them
foreach (char c in permissions)
{
//false = don't raise events since we'll raise a custom event after
MakeNew(user, node, c, false);
}
OnUpdated(new UserPermission(user, node, permissions.ToCharArray()), new SaveEventArgs());
}