Fixes: #U4-2041, #U4-2040 - user cache is not invalidated when permissions change across LB environments, also streamlined how caching is handled in
these classes which now use the standardized events way to do things. Fixes a performance issue and ensures that the cache is not invalidated for 'micro' operations during permission changes.
This commit is contained in:
@@ -77,6 +77,12 @@ namespace Umbraco.Web.Cache
|
||||
User.Saving += UserSaving;
|
||||
User.Deleting += UserDeleting;
|
||||
|
||||
//Bind to permission events
|
||||
|
||||
Permission.New += PermissionNew;
|
||||
Permission.Updated += PermissionUpdated;
|
||||
Permission.Deleted += PermissionDeleted;
|
||||
|
||||
//Bind to template events
|
||||
//NOTE: we need to bind to legacy and new API events currently: http://issues.umbraco.org/issue/U4-1979
|
||||
|
||||
@@ -105,7 +111,7 @@ namespace Umbraco.Web.Cache
|
||||
MediaService.Moving += MediaServiceMoving;
|
||||
MediaService.Trashing += MediaServiceTrashing;
|
||||
}
|
||||
|
||||
|
||||
#region Dictionary event handlers
|
||||
|
||||
static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, Core.Events.SaveEventArgs<IDictionaryItem> e)
|
||||
@@ -301,6 +307,22 @@ namespace Umbraco.Web.Cache
|
||||
#endregion
|
||||
|
||||
#region User event handlers
|
||||
|
||||
static void PermissionDeleted(UserPermission sender, DeleteEventArgs e)
|
||||
{
|
||||
InvalidateCacheForPermissionsChange(sender);
|
||||
}
|
||||
|
||||
static void PermissionUpdated(UserPermission sender, SaveEventArgs e)
|
||||
{
|
||||
InvalidateCacheForPermissionsChange(sender);
|
||||
}
|
||||
|
||||
static void PermissionNew(UserPermission sender, NewEventArgs e)
|
||||
{
|
||||
InvalidateCacheForPermissionsChange(sender);
|
||||
}
|
||||
|
||||
static void UserDeleting(User sender, System.EventArgs e)
|
||||
{
|
||||
DistributedCache.Instance.RemoveUserCache(sender.Id);
|
||||
@@ -309,7 +331,24 @@ namespace Umbraco.Web.Cache
|
||||
static void UserSaving(User sender, System.EventArgs e)
|
||||
{
|
||||
DistributedCache.Instance.RefreshUserCache(sender.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvalidateCacheForPermissionsChange(UserPermission sender)
|
||||
{
|
||||
if (sender.User != null)
|
||||
{
|
||||
DistributedCache.Instance.RefreshUserCache(sender.User.Id);
|
||||
}
|
||||
if (sender.UserId > -1)
|
||||
{
|
||||
DistributedCache.Instance.RefreshUserCache(sender.UserId);
|
||||
}
|
||||
if (sender.Nodes.Any())
|
||||
{
|
||||
DistributedCache.Instance.RefreshAllUserCache();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Template event handlers
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace Umbraco.Web.Cache
|
||||
public static void RefreshUserCache(this DistributedCache dc, int userId)
|
||||
{
|
||||
dc.Refresh(new Guid(DistributedCache.UserCacheRefresherId), userId);
|
||||
}
|
||||
|
||||
public static void RefreshAllUserCache(this DistributedCache dc)
|
||||
{
|
||||
dc.RefreshAll(new Guid(DistributedCache.UserCacheRefresherId));
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
@@ -7,6 +8,7 @@ using System.Collections;
|
||||
using System.Web.UI.WebControls;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data;
|
||||
using Umbraco.Web.Security;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic;
|
||||
using System.Web;
|
||||
@@ -43,39 +45,34 @@ namespace umbraco.cms.presentation.user
|
||||
/// <param name="nodeIDs"></param>
|
||||
/// <param name="actions"></param>
|
||||
/// <param name="replaceChildren"></param>
|
||||
public void SaveNewPermissions(int[] nodeIDs, List<umbraco.interfaces.IAction> actions, bool replaceChildren)
|
||||
public void SaveNewPermissions(int[] nodeIDs, List<IAction> actions, bool replaceChildren)
|
||||
{
|
||||
//ensure permissions that are permission assignable
|
||||
List<IAction> permissions = actions.FindAll(
|
||||
delegate(IAction a)
|
||||
{
|
||||
return (a.CanBePermissionAssigned);
|
||||
}
|
||||
);
|
||||
var permissions = actions.FindAll(a => (a.CanBePermissionAssigned));
|
||||
|
||||
//ensure that only the nodes that the user has permissions to update are updated
|
||||
List<int> lstNoPermissions = new List<int>();
|
||||
foreach (int nodeID in nodeIDs)
|
||||
var lstNoPermissions = new List<int>();
|
||||
foreach (var nodeId in nodeIDs)
|
||||
{
|
||||
string nodeActions = UmbracoEnsuredPage.CurrentUser.GetPermissions(GetNodePath(nodeID));
|
||||
List<IAction> lstActions = umbraco.BusinessLogic.Actions.Action.FromString(nodeActions);
|
||||
var nodeActions = WebSecurity.CurrentUser.GetPermissions(GetNodePath(nodeId));
|
||||
var lstActions = BusinessLogic.Actions.Action.FromString(nodeActions);
|
||||
if (lstActions == null || !lstActions.Contains(ActionRights.Instance))
|
||||
lstNoPermissions.Add(nodeID);
|
||||
lstNoPermissions.Add(nodeId);
|
||||
}
|
||||
//remove the nodes that the user doesn't have permission to update
|
||||
List<int> lstNodeIDs = new List<int>();
|
||||
var lstNodeIDs = new List<int>();
|
||||
lstNodeIDs.AddRange(nodeIDs);
|
||||
foreach (int noPermission in lstNoPermissions)
|
||||
foreach (var noPermission in lstNoPermissions)
|
||||
lstNodeIDs.Remove(noPermission);
|
||||
nodeIDs = lstNodeIDs.ToArray();
|
||||
|
||||
//get the complete list of node ids that this change will affect
|
||||
List<int> allNodes = new List<int>();
|
||||
var allNodes = new List<int>();
|
||||
if (replaceChildren)
|
||||
foreach (int nodeID in nodeIDs)
|
||||
foreach (var nodeId in nodeIDs)
|
||||
{
|
||||
allNodes.Add(nodeID);
|
||||
allNodes.AddRange(FindChildNodes(nodeID));
|
||||
allNodes.Add(nodeId);
|
||||
allNodes.AddRange(FindChildNodes(nodeId));
|
||||
}
|
||||
else
|
||||
allNodes.AddRange(nodeIDs);
|
||||
@@ -85,18 +82,18 @@ namespace umbraco.cms.presentation.user
|
||||
|
||||
//if permissions are to be assigned, then assign them
|
||||
if (permissions.Count > 0)
|
||||
foreach (umbraco.interfaces.IAction oPer in permissions)
|
||||
{
|
||||
foreach (var oPer in permissions)
|
||||
{
|
||||
InsertPermissions(allNodes.ToArray(), oPer);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
//clear umbraco cache (this is the exact syntax umbraco uses... which should be a public method).
|
||||
HttpRuntime.Cache.Remove(string.Format("UmbracoUser{0}", m_user.Id.ToString()));
|
||||
//TODO:can also set a user property which will flush the cache!
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,18 +151,8 @@ namespace umbraco.cms.presentation.user
|
||||
|
||||
private void InsertPermissions(IEnumerable<int> nodeIDs, IAction permission)
|
||||
{
|
||||
foreach (int i in nodeIDs)
|
||||
InsertPermission(i, permission);
|
||||
Permission.MakeNew(m_user, nodeIDs.Select(x => new CMSNode(x)), permission.Letter, true);
|
||||
}
|
||||
|
||||
private void InsertPermission(int nodeId, IAction permission)
|
||||
{
|
||||
//create a new CMSNode object but don't initialize (this prevents a db query)
|
||||
var node = new CMSNode(nodeId, false);
|
||||
Permission.MakeNew(m_user, node, permission.Letter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,21 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Umbraco.Core.Events;
|
||||
using umbraco.DataLayer;
|
||||
using umbraco.cms.businesslogic;
|
||||
using System.Collections.Generic;
|
||||
using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs;
|
||||
|
||||
namespace umbraco.BusinessLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for Permission.
|
||||
/// </summary>
|
||||
public class Permission
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for Permission.
|
||||
/// </summary>
|
||||
public class Permission
|
||||
{
|
||||
|
||||
public int NodeId { get; private set; }
|
||||
public int UserId { get; private set; }
|
||||
@@ -30,23 +32,44 @@ namespace umbraco.BusinessLogic
|
||||
get { return Application.SqlHelper; }
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void MakeNew(BusinessLogic.User User, cms.businesslogic.CMSNode Node, char PermissionKey)
|
||||
{
|
||||
IParameter[] parameters = new IParameter[] { SqlHelper.CreateParameter("@userId", User.Id),
|
||||
SqlHelper.CreateParameter("@nodeId", Node.Id),
|
||||
SqlHelper.CreateParameter("@permission", PermissionKey.ToString()) };
|
||||
|
||||
// Method is synchronized so exists remains consistent (avoiding race condition)
|
||||
bool exists = SqlHelper.ExecuteScalar<int>("SELECT COUNT(userId) FROM umbracoUser2nodePermission WHERE userId = @userId AND nodeId = @nodeId AND permission = @permission",
|
||||
parameters) > 0;
|
||||
if (!exists) {
|
||||
SqlHelper.ExecuteNonQuery("INSERT INTO umbracoUser2nodePermission (userId, nodeId, permission) VALUES (@userId, @nodeId, @permission)",
|
||||
parameters);
|
||||
// clear user cache to ensure permissions are re-loaded
|
||||
User.GetUser(User.Id).FlushFromCache();
|
||||
public static void MakeNew(User User, CMSNode Node, char PermissionKey)
|
||||
{
|
||||
MakeNew(User, Node, PermissionKey, true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
internal static void MakeNew(User user, IEnumerable<CMSNode> nodes, char permissionKey, bool raiseEvents)
|
||||
{
|
||||
var asArray = nodes.ToArray();
|
||||
foreach (var node in asArray)
|
||||
{
|
||||
var parameters = new[] { SqlHelper.CreateParameter("@userId", user.Id),
|
||||
SqlHelper.CreateParameter("@nodeId", node.Id),
|
||||
SqlHelper.CreateParameter("@permission", permissionKey.ToString()) };
|
||||
|
||||
// Method is synchronized so exists remains consistent (avoiding race condition)
|
||||
var exists = SqlHelper.ExecuteScalar<int>(
|
||||
"SELECT COUNT(userId) FROM umbracoUser2nodePermission WHERE userId = @userId AND nodeId = @nodeId AND permission = @permission",
|
||||
parameters) > 0;
|
||||
|
||||
if (exists) return;
|
||||
|
||||
SqlHelper.ExecuteNonQuery(
|
||||
"INSERT INTO umbracoUser2nodePermission (userId, nodeId, permission) VALUES (@userId, @nodeId, @permission)",
|
||||
parameters);
|
||||
}
|
||||
}
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
OnNew(new UserPermission(user, asArray, new[] { permissionKey }), new NewEventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
private static void MakeNew(User User, CMSNode Node, char PermissionKey, bool raiseEvents)
|
||||
{
|
||||
MakeNew(User, new[] {Node}, PermissionKey, raiseEvents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the permissions for a user
|
||||
@@ -74,7 +97,7 @@ namespace umbraco.BusinessLogic
|
||||
/// <summary>
|
||||
/// Returns the permissions for a node
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Permission> GetNodePermissions(CMSNode node)
|
||||
{
|
||||
@@ -100,10 +123,20 @@ namespace umbraco.BusinessLogic
|
||||
/// <param name="user"></param>
|
||||
/// <param name="node"></param>
|
||||
public static void DeletePermissions(User user, CMSNode node)
|
||||
{
|
||||
DeletePermissions(user, node, true);
|
||||
}
|
||||
|
||||
internal static void DeletePermissions(User user, CMSNode node, bool raiseEvents)
|
||||
{
|
||||
// delete all settings on the node for this user
|
||||
SqlHelper.ExecuteNonQuery("delete from umbracoUser2NodePermission where userId = @userId and nodeId = @nodeId",
|
||||
SqlHelper.CreateParameter("@userId", user.Id), SqlHelper.CreateParameter("@nodeId", node.Id));
|
||||
SqlHelper.CreateParameter("@userId", user.Id), SqlHelper.CreateParameter("@nodeId", node.Id));
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
OnDeleted(new UserPermission(user, node, null), new DeleteEventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -114,20 +147,23 @@ namespace umbraco.BusinessLogic
|
||||
{
|
||||
// delete all settings on the node for this user
|
||||
SqlHelper.ExecuteNonQuery("delete from umbracoUser2NodePermission where userId = @userId",
|
||||
SqlHelper.CreateParameter("@userId", user.Id));
|
||||
SqlHelper.CreateParameter("@userId", user.Id));
|
||||
|
||||
OnDeleted(new UserPermission(user, Enumerable.Empty<CMSNode>(), null), new DeleteEventArgs());
|
||||
}
|
||||
|
||||
public static void DeletePermissions(int iUserID, int[] iNodeIDs)
|
||||
{
|
||||
string sql = "DELETE FROM umbracoUser2NodePermission WHERE nodeID IN ({0}) AND userID=@userID";
|
||||
string nodeIDs = string.Join(",", Array.ConvertAll<int, string>(iNodeIDs, Converter));
|
||||
var sql = "DELETE FROM umbracoUser2NodePermission WHERE nodeID IN ({0}) AND userID=@userID";
|
||||
var nodeIDs = string.Join(",", Array.ConvertAll(iNodeIDs, Converter));
|
||||
sql = string.Format(sql, nodeIDs);
|
||||
SqlHelper.ExecuteNonQuery(sql,
|
||||
new IParameter[] { SqlHelper.CreateParameter("@userID", iUserID) });
|
||||
SqlHelper.ExecuteNonQuery(sql, new[] { SqlHelper.CreateParameter("@userID", iUserID) });
|
||||
|
||||
OnDeleted(new UserPermission(iUserID, iNodeIDs), new DeleteEventArgs());
|
||||
}
|
||||
public static void DeletePermissions(int iUserID, int iNodeID)
|
||||
{
|
||||
DeletePermissions(iUserID, new int[] { iNodeID });
|
||||
DeletePermissions(iUserID, new[] { iNodeID });
|
||||
}
|
||||
private static string Converter(int from)
|
||||
{
|
||||
@@ -140,20 +176,122 @@ namespace umbraco.BusinessLogic
|
||||
/// <param name="node"></param>
|
||||
public static void DeletePermissions(CMSNode node)
|
||||
{
|
||||
|
||||
SqlHelper.ExecuteNonQuery("delete from umbracoUser2NodePermission where nodeId = @nodeId",
|
||||
SqlHelper.ExecuteNonQuery(
|
||||
"delete from umbracoUser2NodePermission where nodeId = @nodeId",
|
||||
SqlHelper.CreateParameter("@nodeId", node.Id));
|
||||
|
||||
OnDeleted(new UserPermission(null, node, null), new DeleteEventArgs());
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void UpdateCruds(User user, CMSNode node, string permissions)
|
||||
{
|
||||
// delete all settings on the node for this user
|
||||
DeletePermissions(user, node);
|
||||
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);
|
||||
|
||||
// Loop through the permissions and create them
|
||||
foreach (char c in permissions.ToCharArray())
|
||||
MakeNew(user, node, c);
|
||||
}
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
|
||||
internal static event TypedEventHandler<UserPermission, DeleteEventArgs> Deleted;
|
||||
private static void OnDeleted(UserPermission permission, DeleteEventArgs args)
|
||||
{
|
||||
if (Deleted != null)
|
||||
{
|
||||
Deleted(permission, args);
|
||||
}
|
||||
}
|
||||
|
||||
internal static event TypedEventHandler<UserPermission, SaveEventArgs> Updated;
|
||||
private static void OnUpdated(UserPermission permission, SaveEventArgs args)
|
||||
{
|
||||
if (Updated != null)
|
||||
{
|
||||
Updated(permission, args);
|
||||
}
|
||||
}
|
||||
|
||||
internal static event TypedEventHandler<UserPermission, NewEventArgs> New;
|
||||
private static void OnNew(UserPermission permission, NewEventArgs args)
|
||||
{
|
||||
if (New != null)
|
||||
{
|
||||
New(permission, args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class UserPermission
|
||||
{
|
||||
private int? _userId;
|
||||
private readonly int[] _nodeIds;
|
||||
|
||||
internal UserPermission(int userId)
|
||||
{
|
||||
_userId = userId;
|
||||
}
|
||||
|
||||
internal UserPermission(int userId, IEnumerable<int> nodeIds)
|
||||
{
|
||||
_userId = userId;
|
||||
_nodeIds = nodeIds.ToArray();
|
||||
}
|
||||
|
||||
internal UserPermission(User user, CMSNode node, char[] permissionKeys)
|
||||
{
|
||||
User = user;
|
||||
Nodes = new[] { node };
|
||||
PermissionKeys = permissionKeys;
|
||||
}
|
||||
|
||||
internal UserPermission(User user, IEnumerable<CMSNode> nodes, char[] permissionKeys)
|
||||
{
|
||||
User = user;
|
||||
Nodes = nodes;
|
||||
PermissionKeys = permissionKeys;
|
||||
}
|
||||
|
||||
internal int UserId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_userId.HasValue)
|
||||
{
|
||||
return _userId.Value;
|
||||
}
|
||||
if (User != null)
|
||||
{
|
||||
return User.Id;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<int> NodeIds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_nodeIds != null)
|
||||
{
|
||||
return _nodeIds;
|
||||
}
|
||||
if (Nodes != null)
|
||||
{
|
||||
return Nodes.Select(x => x.Id);
|
||||
}
|
||||
return Enumerable.Empty<int>();
|
||||
}
|
||||
}
|
||||
|
||||
internal User User { get; private set; }
|
||||
internal IEnumerable<CMSNode> Nodes { get; private set; }
|
||||
internal char[] PermissionKeys { get; private set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user