diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java
index 77e5855894..fe037be6b2 100644
--- a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java
+++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java
@@ -25,17 +25,26 @@
*/
package org.alfresco.rest.rules;
+import static org.alfresco.rest.requests.RuleSettings.IS_INHERITANCE_ENABLED;
import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModel;
+import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithModifiedValues;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
+import java.util.List;
+
+import com.google.common.collect.ImmutableMap;
+
import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestRuleModel;
+import org.alfresco.rest.model.RestRuleSetLinkModel;
import org.alfresco.rest.model.RestRuleSetModel;
import org.alfresco.rest.model.RestRuleSetModelsCollection;
import org.alfresco.rest.model.RestRuleSettingsModel;
+import org.alfresco.rest.requests.coreAPI.RestCoreAPI;
+import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
@@ -248,4 +257,78 @@ public class GetRuleSetsTests extends RestTest
ruleSet.assertThat().field("owningFolder").is(ruleFolder.getNodeRef())
.assertThat().field("id").is(ruleSetId);
}
+
+ /**
+ * Check we can find out the id of any folders that inherit a rule set.
+ *
+ * The test checks several different situations:
+ *
+ * folder --[owns]-> rule set
+ * +- publicFolder --[inherits]-> rule set (user has access)
+ * +- privateFolder --[inherits]-> rule set (user does not have access)
+ * +- publicGrandchild --[inherits]-> rule set (user has access again)
+ * +- nonInheritingFolder (inheritance should be prevented)
+ * +- linkingFolder --[links]-> rule set (not inherited)
+ * +- descendantFolder --[inherits]-> rule set (inherited via link)
+ *
+ */
+ @Test (groups = { TestGroup.REST_API, TestGroup.RULES })
+ public void getRuleSetAndInheritedBy()
+ {
+ STEP("Create a site owned by admin and add user as a contributor");
+ SiteModel siteModel = dataSite.usingAdmin().createPrivateRandomSite();
+ dataUser.addUserToSite(user, siteModel, UserRole.SiteContributor);
+
+ STEP("Create the folder structure");
+ FolderModel folder = dataContent.usingUser(user).usingSite(siteModel).createFolder();
+ FolderModel publicFolder = dataContent.usingUser(user).usingResource(folder).createFolder();
+ FolderModel privateFolder = dataContent.usingAdmin().usingResource(folder).createFolder();
+ dataContent.usingAdmin().usingResource(privateFolder).setInheritPermissions(false);
+ // Create the grandchild with user and use admin to move it under the private folder.
+ FolderModel publicGrandchild = dataContent.usingUser(user).usingSite(siteModel).createFolder();
+ coreAPIForAdmin().usingActions().executeAction("move", publicGrandchild, ImmutableMap.of("destination-folder", "workspace://SpacesStore/" + privateFolder.getNodeRef()));
+ // Create the non-inheriting folder.
+ FolderModel nonInheritingFolder = dataContent.usingUser(user).usingResource(folder).createFolder();
+ RestRuleSettingsModel nonInheriting = new RestRuleSettingsModel();
+ nonInheriting.setKey(IS_INHERITANCE_ENABLED);
+ nonInheriting.setValue(false);
+ coreAPIForUser().usingNode(nonInheritingFolder).usingIsInheritanceEnabledRuleSetting().updateSetting(nonInheriting);
+ // Create a child that will link to the rule and a child of that to inherit via the link.
+ FolderModel linkingFolder = dataContent.usingUser(user).usingResource(nonInheritingFolder).createFolder();
+ FolderModel descendantFolder = dataContent.usingUser(user).usingResource(linkingFolder).createFolder();
+
+ STEP("Create an inheritable rule in the folder and get the rule set id.");
+ RestRuleModel ruleModel = createRuleModelWithModifiedValues();
+ coreAPIForUser().usingNode(folder).usingDefaultRuleSet().createSingleRule(ruleModel);
+ RestRuleSetModelsCollection ruleSets = coreAPIForUser().usingNode(folder).getListOfRuleSets();
+ String ruleSetId = ruleSets.getEntries().get(0).onModel().getId();
+
+ STEP("Create the link to the rule from the linking folder");
+ RestRuleSetLinkModel ruleSetLink = new RestRuleSetLinkModel();
+ ruleSetLink.setId(folder.getNodeRef());
+ coreAPIForUser().usingNode(linkingFolder).createRuleLink(ruleSetLink);
+
+ STEP("Remove the user from the site");
+ dataUser.removeUserFromSite(user, siteModel);
+
+ STEP("Get the rule set and inheriting folders");
+ RestRuleSetModel ruleSet = coreAPIForUser().usingNode(folder)
+ .include("inheritedBy")
+ .getRuleSet(ruleSetId);
+
+ restClient.assertStatusCodeIs(OK);
+ List expectedInheritors = List.of(publicFolder.getNodeRef(), descendantFolder.getNodeRef(), publicGrandchild.getNodeRef());
+ ruleSet.assertThat().field("inheritedBy").is(expectedInheritors)
+ .assertThat().field("id").is(ruleSetId);
+ }
+
+ private RestCoreAPI coreAPIForUser()
+ {
+ return restClient.authenticateUser(user).withCoreAPI();
+ }
+
+ private RestCoreAPI coreAPIForAdmin()
+ {
+ return restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI();
+ }
}
diff --git a/pom.xml b/pom.xml
index 6f1520f5cc..8f5e42a9b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,7 +123,7 @@
2.7.4
3.0.49
5.1.1
- 1.115
+ 1.117
1.32
1.9
1.7
diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetLoader.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetLoader.java
index 864462f5d0..9800de58b3 100644
--- a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetLoader.java
+++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetLoader.java
@@ -36,6 +36,7 @@ import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.rule.RuleService;
/** Responsible for converting a NodeRef into a {@link RuleSet} object. */
@Experimental
@@ -43,7 +44,10 @@ public class RuleSetLoader
{
protected static final String OWNING_FOLDER = "owningFolder";
protected static final String INCLUSION_TYPE = "inclusionType";
+ protected static final String INHERITED_BY = "inheritedBy";
+ public static final int MAX_INHERITED_BY_SIZE = 100;
private NodeService nodeService;
+ private RuleService ruleService;
/**
* Load a rule set for the given node ref.
@@ -79,12 +83,26 @@ public class RuleSetLoader
ruleSet.setInclusionType(linked ? LINKED : INHERITED);
}
}
+ if (includes.contains(INHERITED_BY))
+ {
+ ruleSet.setInheritedBy(loadInheritedBy(ruleSetNodeRef));
+ }
}
return ruleSet;
}
+ private List loadInheritedBy(NodeRef ruleSetNodeRef)
+ {
+ return ruleService.getFoldersInheritingRuleSet(ruleSetNodeRef, MAX_INHERITED_BY_SIZE);
+ }
+
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
+
+ public void setRuleService(RuleService ruleService)
+ {
+ this.ruleService = ruleService;
+ }
}
diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java
index 7cddaa45cc..e2719bd4ed 100644
--- a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java
+++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java
@@ -26,6 +26,7 @@
package org.alfresco.rest.api.model.rules;
+import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
@@ -40,6 +41,7 @@ public class RuleSet
private String id;
private NodeRef owningFolder;
private InclusionType inclusionType;
+ private List inheritedBy;
public static RuleSet of(String id)
{
@@ -86,6 +88,16 @@ public class RuleSet
this.inclusionType = inclusionType;
}
+ public List getInheritedBy()
+ {
+ return inheritedBy;
+ }
+
+ public void setInheritedBy(List inheritedBy)
+ {
+ this.inheritedBy = inheritedBy;
+ }
+
@Override
public String toString()
{
@@ -94,6 +106,7 @@ public class RuleSet
.add("id='" + id + "'")
.add("owningFolder='" + owningFolder + "'")
.add("inclusionType='" + inclusionType + "'")
+ .add("inheritedBy='" + inheritedBy + "'")
.toString()
+ '}';
}
@@ -108,13 +121,14 @@ public class RuleSet
RuleSet ruleSet = (RuleSet) o;
return Objects.equals(id, ruleSet.id)
&& Objects.equals(owningFolder, ruleSet.owningFolder)
- && inclusionType == ruleSet.inclusionType;
+ && inclusionType == ruleSet.inclusionType
+ && Objects.equals(inheritedBy, ruleSet.inheritedBy);
}
@Override
public int hashCode()
{
- return Objects.hash(id, owningFolder, inclusionType);
+ return Objects.hash(id, owningFolder, inclusionType, inheritedBy);
}
public static Builder builder()
@@ -127,6 +141,7 @@ public class RuleSet
private String id;
private NodeRef owningFolder;
private InclusionType inclusionType;
+ private List inheritedBy;
public Builder id(String id)
{
@@ -146,12 +161,19 @@ public class RuleSet
return this;
}
+ public Builder inheritedBy(List inheritedBy)
+ {
+ this.inheritedBy = inheritedBy;
+ return this;
+ }
+
public RuleSet create()
{
final RuleSet ruleSet = new RuleSet();
ruleSet.setId(id);
ruleSet.setOwningFolder(owningFolder);
ruleSet.setInclusionType(inclusionType);
+ ruleSet.setInheritedBy(inheritedBy);
return ruleSet;
}
}
diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml
index a4436bc7c0..e4c38b46db 100644
--- a/remote-api/src/main/resources/alfresco/public-rest-context.xml
+++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml
@@ -863,6 +863,7 @@
+
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetLoaderTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetLoaderTest.java
index 29a0fd92c5..f30cef0bf3 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetLoaderTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetLoaderTest.java
@@ -26,11 +26,14 @@
package org.alfresco.rest.api.impl.rules;
import static org.alfresco.rest.api.impl.rules.RuleSetLoader.INCLUSION_TYPE;
+import static org.alfresco.rest.api.impl.rules.RuleSetLoader.INHERITED_BY;
import static org.alfresco.rest.api.impl.rules.RuleSetLoader.OWNING_FOLDER;
import static org.alfresco.rest.api.model.rules.InclusionType.INHERITED;
import static org.alfresco.rest.api.model.rules.InclusionType.LINKED;
import static org.alfresco.rest.api.model.rules.InclusionType.OWNED;
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import java.util.List;
@@ -41,6 +44,7 @@ import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.rule.RuleService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +70,8 @@ public class RuleSetLoaderTest extends TestCase
@Mock
private NodeService nodeServiceMock;
@Mock
+ private RuleService ruleServiceMock;
+ @Mock
private ChildAssociationRef ruleSetAssociationMock;
@Mock
private ChildAssociationRef linkAssociationMock;
@@ -79,6 +85,8 @@ public class RuleSetLoaderTest extends TestCase
given(linkAssociationMock.getParentRef()).willReturn(LINKING_FOLDER);
given(nodeServiceMock.getParentAssocs(RULE_SET_NODE)).willReturn(List.of(ruleSetAssociationMock, linkAssociationMock));
+
+ given(ruleServiceMock.getFoldersInheritingRuleSet(eq(RULE_SET_NODE), anyInt())).willReturn(List.of(INHERITING_FOLDER));
}
@Test
@@ -130,4 +138,14 @@ public class RuleSetLoaderTest extends TestCase
RuleSet expected = RuleSet.builder().id(RULE_SET_ID).inclusionType(INHERITED).create();
assertEquals(expected, actual);
}
+
+ @Test
+ public void testLoadRuleSet_inheritedBy()
+ {
+ // Call the method under test.
+ RuleSet actual = ruleSetLoader.loadRuleSet(RULE_SET_NODE, INHERITING_FOLDER, List.of(INHERITED_BY));
+
+ RuleSet expected = RuleSet.builder().id(RULE_SET_ID).inheritedBy(List.of(INHERITING_FOLDER)).create();
+ assertEquals(expected, actual);
+ }
}
diff --git a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java
index fa92c02204..db591fbbf7 100644
--- a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java
+++ b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java
@@ -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 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 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 getFoldersInheritingRuleSet(NodeRef ruleSet, int maxFoldersToReturn)
+ {
+ // Seed stack with all folders owning or linking to the rule set.
+ Deque stack = new LinkedList<>();
+ for (ChildAssociationRef parentAssociation : runtimeNodeService.getParentAssocs(ruleSet))
+ {
+ stack.add(parentAssociation.getParentRef());
+ }
+ // Process child folders to find all that inherit the rules.
+ List 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 assocs = this.runtimeNodeService.getChildAssocs(ruleFolder, RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX);
+ List assocs = this.runtimeNodeService.getChildAssocs(ruleFolder, MATCH_ALL, ASSOC_NAME_RULES_REGEX);
List orderedAssocs = new ArrayList(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 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)
diff --git a/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java b/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java
index ed42df7e25..3b03eb329b 100644
--- a/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java
+++ b/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java
@@ -227,6 +227,18 @@ public interface RuleService
@Experimental
List 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 getFoldersInheritingRuleSet(NodeRef ruleSet, int maxFoldersToReturn);
+
/**
* Get the rule given its node reference
*
diff --git a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java
index 11f7caebc2..8c085abaea 100644
--- a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java
+++ b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java
@@ -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 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 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 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 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 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 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 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.
+ *
+ * This test uses a chain of three folders:
+ *
+ * grandparent - owns the rule set
+ * parent - user does not have read access
+ * child - user _does_ have read access
+ *
+ */
+ @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 actual = ruleService.getFoldersInheritingRuleSet(ruleSetNode, 100);
+
+ assertEquals("Unexpected list of inheriting folders.", List.of(child), actual);
+ }
}