Added clarifying comments to the logic for granular permissions. (#18705)

This commit is contained in:
Andy Butland
2025-03-18 11:08:30 +01:00
committed by GitHub
parent 42cb9b5c1e
commit b07a24ba55

View File

@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using NUnit.Framework;
@@ -17,7 +15,6 @@ using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
@@ -302,57 +299,109 @@ public class UserServiceTests : UmbracoIntegrationTest
[Test]
public void Calculate_Permissions_For_User_For_Path()
{
// see: http://issues.umbraco.org/issue/U4-10075#comment=67-40085
// for an overview of what this is testing
// Details of what this is testing were originally at http://issues.umbraco.org/issue/U4-10075#comment=67-40085 (and available
// now at https://web.archive.org/web/20211021021851/http://issues.umbraco.org/issue/U4-10075).
// Here is the same information copied and reformatted:
// Here's a mockup of how user group permissions will work with inheritance.
// User (UserX), belongs to these groups which have the following default permission characters applied:
// | Groups | Defaults |
// |--------|----------|
// | G1 | sdf |
// | G2 | sdgk |
// | G3 | fg |
// Thus the aggregate defaults for UserX is "sdfgk"
// As an example node structure, here is the node tree and where any explicit permissions are assigned.
// It shows you the resulting permissions for each node that the user has:
// | Nodes | G1 | G2 | G3 | Result |
// |-------------|------|------|------|-------------------------------------------|
// | - A | | | | sdfgk (Defaults) |
// | - - B | | fr | | sdfgr (Defaults + Explicit) |
// | - - - C | | | qz | sdfrqz (Defaults + Explicit + Inherited) |
// | - - - - D | | | | sdfrqz (Inherited) |
// | - - - - - E | | | | sdfrqz (Inherited) |
// Notice that 'k' is no longer in the result for node B and below.
// Notice that 'g' is no longer in the result for node C and below.
const string path = "-1,1,2,3,4";
var pathIds = path.GetIdsFromPathReversed();
// Setup: 3 groups with different default permissions.
// - Group A has permissions S, D, F.
// - Group B has permissions S, D, G, K.
// - Group C has permissions F, G.
const int groupA = 7;
const int groupB = 8;
const int groupC = 9;
var userGroups = new Dictionary<int, ISet<string>>
{
{groupA, new[] {"S", "D", "F"}.ToHashSet()}, {groupB, new[] {"S", "D", "G", "K"}.ToHashSet()}, {groupC, new[] {"F", "G"}.ToHashSet()}
{ groupA, new[] {"S", "D", "F"}.ToHashSet() },
{ groupB, new[] {"S", "D", "G", "K"}.ToHashSet() },
{ groupC, new[] {"F", "G"}.ToHashSet() }
};
// Setup: combination of default and specific permissions for each group and document.
EntityPermission[] permissions =
{
new(groupA, 1, userGroups[groupA], true), new(groupA, 2, userGroups[groupA], true),
new(groupA, 3, userGroups[groupA], true), new(groupA, 4, userGroups[groupA], true),
new(groupB, 1, userGroups[groupB], true), new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false),
new(groupB, 3, userGroups[groupB], true), new(groupB, 4, userGroups[groupB], true),
new(groupC, 1, userGroups[groupC], true), new(groupC, 2, userGroups[groupC], true),
new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), new(groupC, 4, userGroups[groupC], true)
new(groupA, 1, userGroups[groupA], true), // Comes from the default permission on group A.
new(groupA, 2, userGroups[groupA], true), // Comes from the default permission on group A.
new(groupA, 3, userGroups[groupA], true), // Comes from the default permission on group A.
new(groupA, 4, userGroups[groupA], true), // Comes from the default permission on group A.
new(groupB, 1, userGroups[groupB], true), // Comes from the default permission on group B.
new(groupB, 2, new[] {"F", "R"}.ToHashSet(), false), // Group B has a specific permission on document 2 of of F, R.
new(groupB, 3, userGroups[groupB], true), // Comes from the default permission on group B.
new(groupB, 4, userGroups[groupB], true), // Comes from the default permission on group B.
new(groupC, 1, userGroups[groupC], true), // Comes from the default permission on group C.
new(groupC, 2, userGroups[groupC], true), // Comes from the default permission on group C.
new(groupC, 3, new[] {"Q", "Z"}.ToHashSet(), false), // Group C has a specific permission on document 3 of of Q, Z.
new(groupC, 4, userGroups[groupC], true) // Comes from the default permission on group C.
};
// Permissions for Id 4
// Permissions for document 4
var result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds);
Assert.AreEqual(4, result.EntityId);
var allPermissions = result.GetAllPermissions().ToArray();
Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions));
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" }));
// Permissions for Id 3
// - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3).
// - doesn't have K or G due to specific permissions set on group B, document 2.
Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"]));
// Permissions for document 3
result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(1).ToArray());
Assert.AreEqual(3, result.EntityId);
allPermissions = result.GetAllPermissions().ToArray();
Assert.AreEqual(6, allPermissions.Length, string.Join(",", allPermissions));
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "R", "Q", "Z" }));
// Permissions for Id 2
// - has S, D, F from group A, R from group B (specific permission on document 2), Q, Z from group C (specific permission on document 3).
// - doesn't have K or G due to specific permissions set on group B, document 2.
Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "R", "Q", "Z"]));
// Permissions for document 2
result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(2).ToArray());
Assert.AreEqual(2, result.EntityId);
allPermissions = result.GetAllPermissions().ToArray();
Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions));
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "R" }));
// Permissions for Id 1
// - has S, D, F from group A, G from group C, R from group B (specific permission on document 2).
// - doesn't have K due to specific permissions set on group B, document 2.
// - this would remove G too, but it's there as it's also defined in group C that doesn't have a specific permission override.
// - doesn't have Q, Z as these specific permissions are added to a document further down the path.
Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "R"]));
// Permissions for document 1
result = UserService.CalculatePermissionsForPathForUser(permissions, pathIds.Skip(3).ToArray());
Assert.AreEqual(1, result.EntityId);
allPermissions = result.GetAllPermissions().ToArray();
Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions));
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" }));
// - has S, D, F from group A, G, K from group B (also G from group C)
Assert.IsTrue(allPermissions.ContainsAll(["S", "D", "F", "G", "K"]));
}
[Test]