ACS-3363 Support inheritedBy in GET rule sets. (#1387)

* ACS-3363 E2E test for inheritedBy.

* ACS-3363 Support optional inheritedBy field in GET rule sets.

* ACS-3363 Update to new version of TAS REST API.

* ACS-3363 Remove user from private site before calling method under test.
This commit is contained in:
Tom Page
2022-09-16 11:06:17 +01:00
committed by GitHub
parent e66263a5a8
commit ac1a77156e
9 changed files with 355 additions and 21 deletions

View File

@@ -25,14 +25,19 @@
*/
package org.alfresco.repo.rule;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES;
import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER;
import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED;
import static org.alfresco.service.namespace.RegexQNamePattern.MATCH_ALL;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -70,7 +75,6 @@ import org.alfresco.service.cmr.rule.Rule;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.rule.RuleServiceException;
import org.alfresco.service.cmr.rule.RuleType;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
@@ -520,7 +524,7 @@ public class RuleServiceImpl
// https://issues.alfresco.com/browse/ETWOTWO-438
if (!runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) ||
permissionService.hasPermission(nodeRef, PermissionService.READ) != AccessStatus.ALLOWED)
permissionService.hasPermission(nodeRef, PermissionService.READ) != ALLOWED)
{
// Doesn't have the aspect or the user doesn't have access
return Collections.emptyList();
@@ -538,7 +542,7 @@ public class RuleServiceImpl
{
// Get the rules for this node
List<ChildAssociationRef> ruleChildAssocRefs =
this.runtimeNodeService.getChildAssocs(ruleFolder, RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX);
this.runtimeNodeService.getChildAssocs(ruleFolder, MATCH_ALL, ASSOC_NAME_RULES_REGEX);
for (ChildAssociationRef ruleChildAssocRef : ruleChildAssocRefs)
{
// Create the rule and add to the list
@@ -567,7 +571,7 @@ public class RuleServiceImpl
{
// Get the rules for this node
List<ChildAssociationRef> ruleChildAssocRefs =
this.runtimeNodeService.getChildAssocs(ruleFolder, RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX);
this.runtimeNodeService.getChildAssocs(ruleFolder, MATCH_ALL, ASSOC_NAME_RULES_REGEX);
ruleCount = ruleChildAssocRefs.size();
}
@@ -647,6 +651,44 @@ public class RuleServiceImpl
return returnList;
}
/** {@inheritDoc} */
@Override
@Experimental
public List<NodeRef> getFoldersInheritingRuleSet(NodeRef ruleSet, int maxFoldersToReturn)
{
// Seed stack with all folders owning or linking to the rule set.
Deque<NodeRef> stack = new LinkedList<>();
for (ChildAssociationRef parentAssociation : runtimeNodeService.getParentAssocs(ruleSet))
{
stack.add(parentAssociation.getParentRef());
}
// Process child folders to find all that inherit the rules.
List<NodeRef> inheritors = new ArrayList<>();
while (!stack.isEmpty() && inheritors.size() < maxFoldersToReturn)
{
NodeRef folder = stack.pop();
runtimeNodeService.getChildAssocs(folder, ASSOC_CONTAINS, MATCH_ALL).stream().map(ChildAssociationRef::getChildRef).forEach(childNode -> {
QName childType = runtimeNodeService.getType(childNode);
if (dictionaryService.isSubClass(childType, ContentModel.TYPE_FOLDER)
&& !runtimeNodeService.hasAspect(childNode, ASPECT_IGNORE_INHERITED_RULES))
{
stack.add(childNode);
// Only return nodes that the user has permission to view.
if (permissionService.hasReadPermission(childNode) == ALLOWED)
{
inheritors.add(childNode);
if (inheritors.size() == maxFoldersToReturn)
{
// Return once we've hit the limit.
return;
}
}
}
});
}
return inheritors;
}
/**
* Gets the inherited rules for a given node reference
*
@@ -798,7 +840,7 @@ public class RuleServiceImpl
{
checkForLinkedRules(nodeRef);
if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) != AccessStatus.ALLOWED)
if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) != ALLOWED)
{
throw new RuleServiceException("Insufficient permissions to save a rule.");
}
@@ -823,7 +865,7 @@ public class RuleServiceImpl
// Create the action node
ruleNodeRef = this.nodeService.createNode(
getSavedRuleFolderRef(nodeRef),
ContentModel.ASSOC_CONTAINS,
ASSOC_CONTAINS,
QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()),
RuleModel.TYPE_RULE).getChildRef();
@@ -866,7 +908,7 @@ public class RuleServiceImpl
NodeRef ruleFolder = getSavedRuleFolderRef(nodeRef);
if (ruleFolder != null)
{
List<ChildAssociationRef> assocs = this.runtimeNodeService.getChildAssocs(ruleFolder, RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX);
List<ChildAssociationRef> assocs = this.runtimeNodeService.getChildAssocs(ruleFolder, MATCH_ALL, ASSOC_NAME_RULES_REGEX);
List<ChildAssociationRef> orderedAssocs = new ArrayList<ChildAssociationRef>(assocs.size());
ChildAssociationRef movedAssoc = null;
for (ChildAssociationRef assoc : assocs)
@@ -948,7 +990,7 @@ public class RuleServiceImpl
{
checkForLinkedRules(nodeRef);
if (permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED)
if (permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == ALLOWED)
{
if (nodeService.exists(nodeRef) && nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES))
{
@@ -1011,7 +1053,7 @@ public class RuleServiceImpl
{
checkForLinkedRules(nodeRef);
if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED)
if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == ALLOWED)
{
if (this.nodeService.exists(nodeRef) == true &&
this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true)
@@ -1021,7 +1063,7 @@ public class RuleServiceImpl
{
List<ChildAssociationRef> ruleChildAssocs = this.nodeService.getChildAssocs(
folder,
RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX);
MATCH_ALL, ASSOC_NAME_RULES_REGEX);
for (ChildAssociationRef ruleChildAssoc : ruleChildAssocs)
{
this.nodeService.removeChild(folder, ruleChildAssoc.getChildRef());
@@ -1371,7 +1413,7 @@ public class RuleServiceImpl
{
boolean result = true;
if (this.nodeService.exists(actionedUponNodeRef)
&& this.permissionService.hasPermission(actionedUponNodeRef, PermissionService.READ).equals(AccessStatus.ALLOWED))
&& this.permissionService.hasPermission(actionedUponNodeRef, PermissionService.READ).equals(ALLOWED))
{
NodeRef copiedFrom = copyService.getOriginal(actionedUponNodeRef);
if (logger.isDebugEnabled() == true)

View File

@@ -227,6 +227,18 @@ public interface RuleService
@Experimental
List<NodeRef> getNodesSupplyingRuleSets(NodeRef nodeRef);
/**
* Get a list of folders inheriting the specified rule set.
*
* @param ruleSet The rule set node.
* @param maxFoldersToReturn A limit on the number of folders to return (since otherwise this could traverse a very large proportion of
* the repository.
* @return The list of the specified
*/
@Auditable (parameters = { "ruleSet", "maxFoldersToReturn" })
@Experimental
List<NodeRef> getFoldersInheritingRuleSet(NodeRef ruleSet, int maxFoldersToReturn);
/**
* Get the rule given its node reference
*

View File

@@ -25,17 +25,21 @@
*/
package org.alfresco.repo.rule;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.model.ContentModel.ASSOC_MEMBER;
import static org.alfresco.model.ContentModel.TYPE_CONTENT;
import static org.alfresco.model.ContentModel.TYPE_FOLDER;
import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES;
import static org.alfresco.repo.rule.RuleModel.ASSOC_ACTION;
import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER;
import static org.alfresco.repo.rule.RuleModel.TYPE_RULE;
import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED;
import static org.alfresco.service.cmr.security.AccessStatus.DENIED;
import static org.alfresco.service.namespace.RegexQNamePattern.MATCH_ALL;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
@@ -52,18 +56,19 @@ import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import java.io.Serializable;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.RuntimeActionService;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -98,6 +103,8 @@ public class RuleServiceImplUnitTest
@Mock
private RuntimeActionService runtimeActionService;
@Mock
private DictionaryService dictionaryService;
@Mock
private Rule mockRule;
@Mock
private Action mockAction;
@@ -106,6 +113,10 @@ public class RuleServiceImplUnitTest
public void setUp()
{
openMocks(this);
when(dictionaryService.isSubClass(TYPE_FOLDER, TYPE_FOLDER)).thenReturn(true);
when(dictionaryService.isSubClass(TYPE_CONTENT, TYPE_FOLDER)).thenReturn(false);
when(permissionService.hasReadPermission(any())).thenReturn(ALLOWED);
}
@Test
@@ -220,7 +231,7 @@ public class RuleServiceImplUnitTest
@Test
public void testGetRuleSetNode_emptyAssociation()
{
given(runtimeNodeService.getChildAssocs(any(), any(), any())).willReturn(Collections.emptyList());
given(runtimeNodeService.getChildAssocs(any(), any(), any())).willReturn(emptyList());
// when
final NodeRef actualNode = ruleService.getRuleSetNode(FOLDER_NODE);
@@ -264,7 +275,7 @@ public class RuleServiceImplUnitTest
@Test
public void testIsRuleSetAssociatedWithFolder_emptyAssociation()
{
given(runtimeNodeService.getParentAssocs(any(), any(), any())).willReturn(Collections.emptyList());
given(runtimeNodeService.getParentAssocs(any(), any(), any())).willReturn(emptyList());
// when
boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE);
@@ -343,7 +354,7 @@ public class RuleServiceImplUnitTest
@Test
public void testIsRuleAssociatedWithRuleSet_emptyAssociation()
{
given(runtimeNodeService.getParentAssocs(any())).willReturn(Collections.emptyList());
given(runtimeNodeService.getParentAssocs(any())).willReturn(emptyList());
// when
boolean associated = ruleService.isRuleAssociatedWithRuleSet(RULE_NODE, RULE_SET_NODE);
@@ -456,7 +467,7 @@ public class RuleServiceImplUnitTest
{
Map<String, NodeRef> nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E");
// Replace the B,C association with a user group membership association.
ChildAssociationRef memberAssoc = new ChildAssociationRef(ASSOC_MEMBER, nodes.get("B"), ContentModel.TYPE_FOLDER, nodes.get("C"));
ChildAssociationRef memberAssoc = new ChildAssociationRef(ASSOC_MEMBER, nodes.get("B"), TYPE_FOLDER, nodes.get("C"));
given(runtimeNodeService.getParentAssocs(nodes.get("C"))).willReturn(List.of(memberAssoc));
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E"));
@@ -570,10 +581,137 @@ public class RuleServiceImplUnitTest
.filter(assoc -> assoc.endsWith(nodeName))
.map(assoc -> assoc.split(",")[0])
.map(nodeRefMap::get)
.map(parentRef -> new ChildAssociationRef(ASSOC_CONTAINS, parentRef, ContentModel.TYPE_FOLDER, nodeRef))
.map(parentRef -> new ChildAssociationRef(ASSOC_CONTAINS, parentRef, TYPE_FOLDER, nodeRef))
.collect(toList());
given(runtimeNodeService.getParentAssocs(nodeRef)).willReturn(parentAssocs);
});
return nodeRefMap;
}
/** Check that getFoldersInheritingRuleSet returns a child folder. */
@Test
public void testGetFoldersInheritingRuleSet()
{
NodeRef parent = new NodeRef("parent://node/");
NodeRef ruleSetNode = new NodeRef("rule://set/");
ChildAssociationRef ruleSetAssociation = mock(ChildAssociationRef.class);
given(runtimeNodeService.getParentAssocs(ruleSetNode)).willReturn(List.of(ruleSetAssociation));
given(ruleSetAssociation.getParentRef()).willReturn(parent);
NodeRef child = new NodeRef("child://node/");
ChildAssociationRef childAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(parent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(childAssocMock));
given(childAssocMock.getChildRef()).willReturn(child);
given(runtimeNodeService.getType(child)).willReturn(TYPE_FOLDER);
List<NodeRef> actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 100);
assertEquals("Unexpected list of inheriting folders.", List.of(child), actual);
}
/** Check that getFoldersInheritingRuleSet omits a child folder if IGNORE_INHERITED_RULES is applied. */
@Test
public void testGetFoldersInheritingRuleSet_ignoreInheritedRules()
{
NodeRef parent = new NodeRef("parent://node/");
NodeRef ruleSetNode = new NodeRef("rule://set/");
ChildAssociationRef ruleSetAssociation = mock(ChildAssociationRef.class);
given(runtimeNodeService.getParentAssocs(ruleSetNode)).willReturn(List.of(ruleSetAssociation));
given(ruleSetAssociation.getParentRef()).willReturn(parent);
NodeRef child = new NodeRef("child://node/");
ChildAssociationRef childAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(parent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(childAssocMock));
given(childAssocMock.getChildRef()).willReturn(child);
given(runtimeNodeService.getType(child)).willReturn(TYPE_FOLDER);
given(runtimeNodeService.hasAspect(child, ASPECT_IGNORE_INHERITED_RULES)).willReturn(true);
List<NodeRef> actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 100);
assertEquals("Unexpected list of inheriting folders.", emptyList(), actual);
}
/** Check that getFoldersInheritingRuleSet only returns at most the requested number of folders. */
@Test
public void testGetFoldersExceedsLimit()
{
NodeRef root = new NodeRef("root://node/");
NodeRef ruleSetNode = new NodeRef("rule://set/");
ChildAssociationRef ruleSetAssociation = mock(ChildAssociationRef.class);
given(runtimeNodeService.getParentAssocs(ruleSetNode)).willReturn(List.of(ruleSetAssociation));
given(ruleSetAssociation.getParentRef()).willReturn(root);
// Create a chain of ancestors starting from the root that is 10 folders deep.
List<NodeRef> nodeChain = new ArrayList<>();
nodeChain.add(root);
IntStream.range(0, 10).forEach(index -> {
NodeRef parent = nodeChain.get(nodeChain.size() - 1);
NodeRef child = new NodeRef("chain://node/" + index);
nodeChain.add(child);
ChildAssociationRef childAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(parent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(childAssocMock));
given(childAssocMock.getChildRef()).willReturn(child);
given(runtimeNodeService.getType(child)).willReturn(TYPE_FOLDER);
});
// Request at most 9 folders inheriting the rule.
List<NodeRef> actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 9);
// Check we don't get the root node or the final descendant folder.
assertEquals("Unexpected list of inheriting folders.", nodeChain.subList(1, 10), actual);
}
/** Check that getFoldersInheritingRuleSet doesn't include documents. */
@Test
public void testGetFoldersInheritingRuleSet_ignoreFiles()
{
NodeRef parent = new NodeRef("parent://node/");
NodeRef ruleSetNode = new NodeRef("rule://set/");
ChildAssociationRef ruleSetAssociation = mock(ChildAssociationRef.class);
given(runtimeNodeService.getParentAssocs(ruleSetNode)).willReturn(List.of(ruleSetAssociation));
given(ruleSetAssociation.getParentRef()).willReturn(parent);
NodeRef childDocument = new NodeRef("child://document/");
ChildAssociationRef childAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(parent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(childAssocMock));
given(childAssocMock.getChildRef()).willReturn(childDocument);
given(runtimeNodeService.getType(childDocument)).willReturn(TYPE_CONTENT);
List<NodeRef> actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 100);
assertEquals("Unexpected list of inheriting folders.", emptyList(), actual);
}
/**
* Check that getFoldersInheritingRuleSet does not include folders that the user doesn't have access to.
* <p>
* This test uses a chain of three folders:
* <pre>
* grandparent - owns the rule set
* parent - user does not have read access
* child - user _does_ have read access
* </pre>
*/
@Test
public void testGetFoldersInheritingRuleSet_omitFoldersWithoutReadPermission()
{
NodeRef grandparent = new NodeRef("grandparent://node/");
NodeRef ruleSetNode = new NodeRef("rule://set/");
ChildAssociationRef ruleSetAssociation = mock(ChildAssociationRef.class);
given(runtimeNodeService.getParentAssocs(ruleSetNode)).willReturn(List.of(ruleSetAssociation));
given(ruleSetAssociation.getParentRef()).willReturn(grandparent);
NodeRef parent = new NodeRef("parent://node/");
ChildAssociationRef parentAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(grandparent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(parentAssocMock));
given(parentAssocMock.getChildRef()).willReturn(parent);
given(runtimeNodeService.getType(parent)).willReturn(TYPE_FOLDER);
NodeRef child = new NodeRef("child://node/");
ChildAssociationRef childAssocMock = mock(ChildAssociationRef.class);
given(runtimeNodeService.getChildAssocs(grandparent, ASSOC_CONTAINS, MATCH_ALL)).willReturn(List.of(childAssocMock));
given(childAssocMock.getChildRef()).willReturn(child);
given(runtimeNodeService.getType(child)).willReturn(TYPE_FOLDER);
// The current user doesn't have permission to view the parent node.
given(permissionService.hasReadPermission(parent)).willReturn(DENIED);
List<NodeRef> actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 100);
assertEquals("Unexpected list of inheriting folders.", List.of(child), actual);
}
}