ACS-3657 Allow returning partial list of rule sets.

If a user does not have access to a rule set applied to a node then it will be
excluded from the results, but the user will be able to see the list of other
rule sets.

Also add E2E tests for permissions when viewing rule sets.
This commit is contained in:
Tom Page
2022-10-06 16:35:29 +01:00
parent 2544885f5e
commit 855e2522f2
3 changed files with 145 additions and 2 deletions

View File

@@ -31,6 +31,7 @@ import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithDefault
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.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
@@ -62,6 +63,8 @@ public class GetRuleSetsTests extends RestTest
private UserModel user;
private SiteModel site;
private FolderModel ruleFolder;
/** A folder with a rule in a private site owned by admin. */
private FolderModel privateFolder;
private FolderModel inheritingChildFolder;
private FolderModel notInheritingChildFolder;
private RestRuleModel rule;
@@ -93,6 +96,11 @@ public class GetRuleSetsTests extends RestTest
.getListOfRuleSets();
ruleSets.assertThat().entriesListCountIs(1);
ruleSetId = ruleSets.getEntries().get(0).onModel().getId();
STEP("Use admin to create a private site containing a rule in a rule set that can be inherited.");
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
privateFolder = dataContent.usingAdmin().usingSite(privateSite).createFolder();
coreAPIForAdmin().usingNode(privateFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithModifiedValues());
}
/** Check we can get an empty list of rule sets. */
@@ -135,6 +143,48 @@ public class GetRuleSetsTests extends RestTest
restClient.assertStatusCodeIs(NOT_FOUND);
}
/** Check that we get a 403 error when trying to get rule sets for a folder we don't have read access to. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getRuleSetsWithoutPermission()
{
STEP("Check a user cannot list rule sets without read access.");
coreAPIForUser().usingNode(privateFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(FORBIDDEN);
}
/** Check that we can still list some rule sets if we don't have permission to view them all. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void permissionsAreRespectedWhenListingRuleSets()
{
STEP("Create a public site containing a parent and child folder with rule inheritance enabled.");
SiteModel publicSite = dataSite.usingUser(user).createPublicRandomSite();
FolderModel parentFolder = dataContent.usingUser(user).usingSite(publicSite).createFolder();
FolderModel childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder();
RestRuleSettingsModel enabled = new RestRuleSettingsModel();
enabled.setValue(true);
coreAPIForUser().usingNode(parentFolder).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled);
STEP("Link the parent folder to a private rule set.");
RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel();
linkModel.setId(privateFolder.getNodeRef());
coreAPIForAdmin().usingNode(parentFolder).createRuleLink(linkModel);
STEP("Create a rule on the child folder.");
coreAPIForUser().usingNode(childFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
STEP("Check admin can view both rule sets.");
RestRuleSetModelsCollection adminViewOfRuleSets = coreAPIForAdmin().usingNode(childFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(OK);
RestRuleSetModel parentRuleSet = adminViewOfRuleSets.getEntries().get(0).onModel();
RestRuleSetModel childRuleSet = adminViewOfRuleSets.getEntries().get(1).onModel();
STEP("Check the normal user can only view the child rule set.");
RestRuleSetModelsCollection userViewOfRuleSets = coreAPIForUser().usingNode(childFolder).getListOfRuleSets();
restClient.assertStatusCodeIs(OK);
userViewOfRuleSets.assertThat().entriesListContains("id", childRuleSet.getId())
.and().entriesListDoesNotContain("id", parentRuleSet.getId());
}
/** Check we can get the id of the folder that owns a list of rule sets. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getRuleSetsAndOwningFolders()
@@ -503,6 +553,38 @@ public class GetRuleSetsTests extends RestTest
ruleSet.assertThat().field("isLinkedTo").is(false);
}
/** Check that we can only view a rule set if have read permission. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void permissionsChecksForFolderWithPrivateAndPublicRuleSets()
{
STEP("Create a public site containing a parent and child folder with rule inheritance enabled.");
SiteModel publicSite = dataSite.usingUser(user).createPublicRandomSite();
FolderModel parentFolder = dataContent.usingUser(user).usingSite(publicSite).createFolder();
FolderModel childFolder = dataContent.usingUser(user).usingResource(parentFolder).createFolder();
RestRuleSettingsModel enabled = new RestRuleSettingsModel();
enabled.setValue(true);
coreAPIForUser().usingNode(parentFolder).usingRuleSetting(IS_INHERITANCE_ENABLED).updateSetting(enabled);
STEP("Link the parent folder to a private rule set.");
RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel();
linkModel.setId(privateFolder.getNodeRef());
coreAPIForAdmin().usingNode(parentFolder).createRuleLink(linkModel);
STEP("Create a rule on the child folder.");
coreAPIForUser().usingNode(childFolder).usingDefaultRuleSet().createSingleRule(createRuleModelWithDefaultValues());
STEP("Use the admin user to get both rule sets.");
RestRuleSetModelsCollection adminViewOfRuleSets = coreAPIForAdmin().usingNode(childFolder).getListOfRuleSets();
RestRuleSetModel parentRuleSet = adminViewOfRuleSets.getEntries().get(0).onModel();
RestRuleSetModel childRuleSet = adminViewOfRuleSets.getEntries().get(1).onModel();
STEP("Check the normal user can only view the child rule set.");
coreAPIForUser().usingNode(childFolder).getRuleSet(parentRuleSet.getId());
restClient.assertStatusCodeIs(FORBIDDEN);
coreAPIForUser().usingNode(childFolder).getRuleSet(childRuleSet.getId());
restClient.assertStatusCodeIs(OK);
}
private RestCoreAPI coreAPIForUser()
{
return restClient.authenticateUser(user).withCoreAPI();

View File

@@ -39,6 +39,7 @@ import java.util.stream.IntStream;
import org.alfresco.repo.rule.RuleModel;
import org.alfresco.repo.rule.RuntimeRuleService;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.rest.api.RuleSets;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.api.model.rules.RuleSetLink;
@@ -50,10 +51,14 @@ import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.rule.RuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Experimental
public class RuleSetsImpl implements RuleSets
{
private static final Logger LOGGER = LoggerFactory.getLogger(RuleSetsImpl.class);
private RuleSetLoader ruleSetLoader;
private RuleService ruleService;
private NodeValidator validator;
@@ -67,15 +72,42 @@ public class RuleSetsImpl implements RuleSets
List<RuleSet> ruleSets = ruleService.getNodesSupplyingRuleSets(folderNode)
.stream()
.map(ruleService::getRuleSetNode)
.map(supplyingNode -> loadRuleSet(supplyingNode, folderNode, includes))
.filter(Objects::nonNull)
.map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes))
.distinct()
.collect(toList());
return ListPage.of(ruleSets, paging);
}
/**
* Load the specified rule set if the user has permission.
*
* @param supplyingNode The folder supplying a rule set.
* @param folderNode The folder being supplied with rule sets.
* @param includes The list of optional fields to include for each rule set in the response.
* @return The rule set from the DB or null if the folder has no rule set, or the current user does not have permission to view it.
*/
private RuleSet loadRuleSet(NodeRef supplyingNode, NodeRef folderNode, List<String> includes)
{
NodeRef ruleSetNode = ruleService.getRuleSetNode(supplyingNode);
// Check if the folder has no rule sets.
if (ruleSetNode == null)
{
return null;
}
try
{
return ruleSetLoader.loadRuleSet(ruleSetNode, folderNode, includes);
}
catch (AccessDeniedException e)
{
LOGGER.debug("User does not have permission to view rule set with id {}.", ruleSetNode, e);
return null;
}
}
@Override
public RuleSet getRuleSetById(String folderNodeId, String ruleSetId, List<String> includes)
{

View File

@@ -42,6 +42,7 @@ import java.util.List;
import junit.framework.TestCase;
import org.alfresco.repo.rule.RuleModel;
import org.alfresco.repo.rule.RuntimeRuleService;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
@@ -146,6 +147,34 @@ public class RuleSetsImplTest extends TestCase
assertEquals(PAGING, actual.getPaging());
}
@Test
public void testOnlyGetPermittedRuleSets()
{
// Simulate a private folder with a rule set that the current user can't access.
NodeRef privateFolder = new NodeRef("private://folder/");
NodeRef privateRuleSetNode = new NodeRef("private://rule/set/node/");
given(ruleServiceMock.getRuleSetNode(privateFolder)).willReturn(privateRuleSetNode);
given(ruleServiceMock.getNodesSupplyingRuleSets(FOLDER_NODE)).willReturn(List.of(FOLDER_NODE, privateFolder));
given(ruleSetLoaderMock.loadRuleSet(eq(privateRuleSetNode), any(NodeRef.class), any(List.class)))
.willThrow(new AccessDeniedException("Cannot access private rule set."));
// Call the method under test.
CollectionWithPagingInfo<RuleSet> actual = ruleSets.getRuleSets(FOLDER_ID, INCLUDES, PAGING);
then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE);
then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE);
then(ruleServiceMock).should().getRuleSetNode(privateFolder);
then(ruleServiceMock).shouldHaveNoMoreInteractions();
// Check we only get the accessible rule set back.
Collection<RuleSet> expected = List.of(ruleSetMock);
assertEquals(expected, actual.getCollection());
assertEquals(PAGING, actual.getPaging());
}
/** Check that a folder with a parent and grandparent can inherit rule sets from the grandparent, even if the parent has no rules. */
@Test
public void testGetInheritedRuleSets()